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

difftreelog

feat(bench) added fee benchmarks for erc721 functions

PraetorP2023-01-25parent: #8dd063b.patch.diff
in: master

3 files changed

modifiedtests/src/benchmarks/mintFee/benchmark.tsdiffbeforeafterboth
before · tests/src/benchmarks/mintFee/benchmark.ts
1import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util';2import {readFile} from 'fs/promises';3import {ContractImports, TokenPermissionField} from '../../eth/util/playgrounds/types';4import {5  ICrossAccountId,6  ITokenPropertyPermission,7} from '../../util/playgrounds/types';8import {IKeyringPair} from '@polkadot/types/types';9import {UniqueNFTCollection} from '../../util/playgrounds/unique';10import {Contract} from 'web3-eth-contract';11import {createObjectCsvWriter} from 'csv-writer';1213const CONTRACT_IMPORT: ContractImports[] = [14  {15    fsPath: `${__dirname}/../../eth/api/CollectionHelpers.sol`,16    solPath: 'eth/api/CollectionHelpers.sol',17  },18  {19    fsPath: `${__dirname}/../../eth/api/ContractHelpers.sol`,20    solPath: 'eth/api/ContractHelpers.sol',21  },22  {23    fsPath: `${__dirname}/../../eth/api/UniqueRefungibleToken.sol`,24    solPath: 'eth/api/UniqueRefungibleToken.sol',25  },26  {27    fsPath: `${__dirname}/../../eth/api/UniqueRefungible.sol`,28    solPath: 'eth/api/UniqueRefungible.sol',29  },30  {31    fsPath: `${__dirname}/../../eth/api/UniqueNFT.sol`,32    solPath: 'eth/api/UniqueNFT.sol',33  },34];3536const PROPERTIES = Array(40)37  .fill(0)38  .map((_, i) => {39    return {40      key: `key_${i}`,41      value: Uint8Array.from(Buffer.from(`value_${i}`)),42    };43  });4445const PERMISSIONS: ITokenPropertyPermission[] = PROPERTIES.map((p) => {46  return {47    key: p.key,48    permission: {49      tokenOwner: true,50      collectionAdmin: true,51      mutable: true,52    },53  };54});5556interface IBenchmarkResultForProp {57	propertiesNumber: number;58	substrateFee: number;59	ethFee: number;60	ethBulkFee: number;61  ethMintCrossFee: number;62	evmProxyContractFee: number;63	evmProxyContractBulkFee: number;64}65interface IBenchmarkCollection {66  createFee: number,67  mintFee: number,68  transferFee: number,69}70const main = async () => {71  const benchmarks = [72    'substrateFee',73    'ethFee',74    'ethBulkFee',75    'ethMintCrossFee',76    'evmProxyContractFee',77    'evmProxyContractBulkFee',78  ];79  const headers = [80    'propertiesNumber',81    ...benchmarks,82  ];838485  const csvWriter = createObjectCsvWriter({86    path: 'properties.csv',87    header: headers,88  });8990  await usingEthPlaygrounds(async (helper, privateKey) => {91    const NOMINAL = helper.balance.getOneTokenNominal();92    const CONTRACT_SOURCE = (93      await readFile(`${__dirname}/proxyContract.sol`)94    ).toString();95    const ZEPPELIN_OBJECT = '0x' + (await readFile(`${__dirname}/openZeppelin/bin/ZeppelinContract.bin`)).toString();96    const ZEPPELIN_ABI = JSON.parse((await readFile(`${__dirname}/openZeppelin/bin/ZeppelinContract.abi`)).toString());9798    const donor = await privateKey('//Alice'); // Seed from account with balance on this network99    const [substrateReceiver] = await helper.arrange.createAccounts([10n], donor);100101    const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);102    const ethReceiver = await helper.eth.createAccountWithBalance(donor, 5n);103104    let susbtrateCollection: UniqueNFTCollection | null;105    const createCollectionSubstrateFee = convertToTokens(106      await helper.arrange.calculcateFee({Substrate: donor.address}, async () => {107        susbtrateCollection = await helper.nft.mintCollection(donor, {tokenPropertyPermissions: [108          {109            key: 'url',110            permission: {111              tokenOwner: true,112              collectionAdmin: true,113              mutable: true,114            },115          },116        ]});117      }),118      NOMINAL,119    );120121    const susbstrateMintFee = convertToTokens(122      await helper.arrange.calculcateFee(123        {Substrate: donor.address},124        () => susbtrateCollection!.mintToken(donor, {Substrate: substrateReceiver.address}, [{key: 'url', value: 'test'}]),125      ),126      NOMINAL,127    );128    const susbstrateTransferFee = convertToTokens(129      await helper.arrange.calculcateFee(130        {Substrate: substrateReceiver.address},131        () => susbtrateCollection!.transferToken(substrateReceiver, 1, {Substrate: donor.address}),132      ),133      NOMINAL,134    );135    const substrateFee: IBenchmarkCollection = {136      createFee: createCollectionSubstrateFee,137      mintFee: susbstrateMintFee,138      transferFee: susbstrateTransferFee,139    };140141    const helperContract = await helper.ethNativeContract.collectionHelpers(ethSigner);142143    let ethContract: Contract | null = null;144145    const createCollectionEthFee = convertToTokens(146      await helper.arrange.calculcateFee({Ethereum: ethSigner}, async () => {147        const result = await helperContract.methods.createNFTCollection('a', 'b', 'c').send({from: ethSigner, value: Number(2n * helper.balance.getOneTokenNominal())});148        ethContract = await helper.ethNativeContract.collection(helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId), 'nft', ethSigner);149      }),150      NOMINAL,151    );152153    await ethContract!.methods.setTokenPropertyPermissions([154      ['url', [155        [TokenPermissionField.Mutable, true],156        [TokenPermissionField.TokenOwner, true],157        [TokenPermissionField.CollectionAdmin, true]],158      ],159    ]).send({from: ethSigner});160161    const ethMintFee = convertToTokens(await helper.arrange.calculcateFee(162      {Ethereum: ethSigner},163      () => ethContract!.methods.mintCross(164        helper.ethCrossAccount.fromAddress(ethReceiver),165        [{key: 'url', value: Buffer.from('test')}],166      )167        .send({from: ethSigner}),168    ), NOMINAL);169170    const ethTransferFee = convertToTokens(await helper.arrange.calculcateFee(171      {Ethereum: ethReceiver},172      () => ethContract!.methods.transferFrom(ethReceiver, ethSigner, 1)173        .send({from: ethReceiver}),174    ), NOMINAL);175176    const ethFee: IBenchmarkCollection = {177      createFee: createCollectionEthFee,178      mintFee: ethMintFee,179      transferFee: ethTransferFee,180    };181182    let zeppelelinContract: Contract | null = null;183184    const zeppelinDeployFee = convertToTokens(185      await helper.arrange.calculcateFee({Ethereum: ethSigner}, async () => {186        zeppelelinContract = await helper.ethContract.deployByAbi(187          ethSigner,188          ZEPPELIN_ABI,189          ZEPPELIN_OBJECT,190        );191      }),192      NOMINAL,193    );194195    const zeppelinMintFee = convertToTokens(await helper.arrange.calculcateFee(196      {Ethereum: ethSigner},197      () => zeppelelinContract!.methods.safeMint(ethReceiver, 'test').send({from: ethSigner}),198    ), NOMINAL);199200201    const zeppelinTransferFee = convertToTokens(await helper.arrange.calculcateFee(202      {Ethereum: ethReceiver},203      () => zeppelelinContract!.methods.safeTransferFrom(ethReceiver, ethSigner, 0).send({from: ethReceiver}),204    ), NOMINAL);205206    const zeppelinFee: IBenchmarkCollection = {207      createFee: zeppelinDeployFee,208      mintFee: zeppelinMintFee,209      transferFee: zeppelinTransferFee,210    };211212    console.log('collection ops fees');213    const results = {substrate: substrateFee, eth: ethFee, zeppelin: zeppelinFee};214    console.table(results);215216    const contract = await helper.ethContract.deployByCode(217      ethSigner,218      'ProxyMint',219      CONTRACT_SOURCE,220      CONTRACT_IMPORT,221    );222223    const fees = await benchMintFee(helper, privateKey, contract);224    console.log('Minting without properties');225    console.table(fees);226227    const result: IBenchmarkResultForProp[] = [];228    const csvResult: IBenchmarkResultForProp[] = [];229230    for (let i = 1; i <= 20; i++) {231      const benchResult = await benchMintWithProperties(helper, privateKey, contract, {232        propertiesNumber: i,233      }) as any;234235      csvResult.push(benchResult);236237      const minFee = Math.min(...(benchmarks.map(x => benchResult[x])));238      for(const key of benchmarks) {239        const keyPercent = Math.round((benchResult[key] / minFee) * 100);240        benchResult[key] = `${benchResult[key]} (${keyPercent}%)`;241      }242243      result.push(benchResult);244    }245246    await csvWriter.writeRecords(csvResult);247248    console.log('Minting with properties');249    console.table(result, headers);250  });251};252253main()254  .then(() => process.exit(0))255  .catch((e) => {256    console.log(e);257    process.exit(1);258  });259260async function createCollectionForBenchmarks(261  helper: EthUniqueHelper,262  privateKey: (seed: string) => Promise<IKeyringPair>,263  ethSigner: string,264  proxyContract: string,265  permissions: ITokenPropertyPermission[],266) {267  const donor = await privateKey('//Alice');268269  const collection = await helper.nft.mintCollection(donor, {270    name: 'test mintToSubstrate',271    description: 'EVMHelpers',272    tokenPrefix: 'ap',273    tokenPropertyPermissions: [274      {275        key: 'url',276        permission: {277          tokenOwner: true,278          collectionAdmin: true,279          mutable: true,280        },281      },282    ],283    limits: {sponsorTransferTimeout: 0, sponsorApproveTimeout: 0},284    permissions: {mintMode: true},285  });286287  await collection.addToAllowList(donor, {288    Ethereum: helper.address.substrateToEth(donor.address),289  });290  await collection.addToAllowList(donor, {Substrate: donor.address});291  await collection.addAdmin(donor, {Ethereum: ethSigner});292  await collection.addAdmin(donor, {293    Ethereum: helper.address.substrateToEth(donor.address),294  });295  await collection.addToAllowList(donor, {Ethereum: proxyContract});296  await collection.addAdmin(donor, {Ethereum: proxyContract});297  await collection.setTokenPropertyPermissions(donor, permissions);298299  return collection;300}301302async function benchMintFee(303  helper: EthUniqueHelper,304  privateKey: (seed: string) => Promise<IKeyringPair>,305  proxyContract: Contract,306): Promise<{307	substrateFee: number;308	ethFee: number;309	evmProxyContractFee: number;310}> {311  const donor = await privateKey('//Alice');312  const substrateReceiver = await privateKey('//Bob');313  const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);314315  const nominal = helper.balance.getOneTokenNominal();316317  await helper.eth.transferBalanceFromSubstrate(318    donor,319    proxyContract.options.address,320    100n,321  );322323  const collection = await createCollectionForBenchmarks(324    helper,325    privateKey,326    ethSigner,327    proxyContract.options.address,328    PERMISSIONS,329  );330331  const substrateFee = await helper.arrange.calculcateFee(332    {Substrate: donor.address},333    () => collection.mintToken(donor, {Substrate: substrateReceiver.address}),334  );335336  const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId);337  const collectionContract = await helper.ethNativeContract.collection(338    collectionEthAddress,339    'nft',340  );341342  const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address);343344  const encodedCall = collectionContract.methods345    .mint(receiverEthAddress)346    .encodeABI();347348  const ethFee = await helper.arrange.calculcateFee(349    {Substrate: donor.address},350    async () => {351      await helper.eth.sendEVM(352        donor,353        collectionContract.options.address,354        encodedCall,355        '0',356      );357    },358  );359360  const evmProxyContractFee = await helper.arrange.calculcateFee(361    {Ethereum: ethSigner},362    async () => {363      await proxyContract.methods364        .mintToSubstrate(365          helper.ethAddress.fromCollectionId(collection.collectionId),366          substrateReceiver.addressRaw,367        )368        .send({from: ethSigner});369    },370  );371372  return {373    substrateFee: convertToTokens(substrateFee, nominal),374    ethFee: convertToTokens(ethFee, nominal),375    evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal),376  };377}378379async function benchMintWithProperties(380  helper: EthUniqueHelper,381  privateKey: (seed: string) => Promise<IKeyringPair>,382  proxyContract: Contract,383  setup: { propertiesNumber: number },384): Promise<IBenchmarkResultForProp> {385  const donor = await privateKey('//Alice'); // Seed from account with balance on this network386  const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);387388  const susbstrateReceiver = await privateKey('//Bob');389  const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address);390391  const nominal = helper.balance.getOneTokenNominal();392393  const substrateFee = await calculateFeeNftMintWithProperties(394    helper,395    privateKey,396    {Substrate: donor.address},397    ethSigner,398    proxyContract.options.address,399    async (collection) => {400      await collection.mintToken(401        donor,402        {Substrate: susbstrateReceiver.address},403        PROPERTIES.slice(0, setup.propertiesNumber).map((p) => {404          return {key: p.key, value: Buffer.from(p.value).toString()};405        }),406      );407    },408  );409410  const ethFee = await calculateFeeNftMintWithProperties(411    helper,412    privateKey,413    {Substrate: donor.address},414    ethSigner,415    proxyContract.options.address,416    async (collection) => {417      const evmContract = await helper.ethNativeContract.collection(418        helper.ethAddress.fromCollectionId(collection.collectionId),419        'nft',420        undefined,421        true,422      );423424      const subTokenId = await evmContract.methods.nextTokenId().call();425426      let encodedCall = evmContract.methods427        .mint(receiverEthAddress)428        .encodeABI();429430      await helper.eth.sendEVM(431        donor,432        evmContract.options.address,433        encodedCall,434        '0',435      );436437      for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) {438        encodedCall = await evmContract.methods439          .setProperty(subTokenId, val.key, Buffer.from(val.value))440          .encodeABI();441442        await helper.eth.sendEVM(443          donor,444          evmContract.options.address,445          encodedCall,446          '0',447        );448      }449    },450  );451452  const ethBulkFee = await calculateFeeNftMintWithProperties(453    helper,454    privateKey,455    {Substrate: donor.address},456    ethSigner,457    proxyContract.options.address,458    async (collection) => {459      const evmContract = await helper.ethNativeContract.collection(460        helper.ethAddress.fromCollectionId(collection.collectionId),461        'nft',462      );463464      const subTokenId = await evmContract.methods.nextTokenId().call();465466      let encodedCall = evmContract.methods467        .mint(receiverEthAddress)468        .encodeABI();469470      await helper.eth.sendEVM(471        donor,472        evmContract.options.address,473        encodedCall,474        '0',475      );476477      encodedCall = await evmContract.methods478        .setProperties(479          subTokenId,480          PROPERTIES.slice(0, setup.propertiesNumber),481        )482        .encodeABI();483484      await helper.eth.sendEVM(485        donor,486        evmContract.options.address,487        encodedCall,488        '0',489      );490    },491  );492493  const ethMintCrossFee = await calculateFeeNftMintWithProperties(494    helper,495    privateKey,496    {Ethereum: ethSigner},497    ethSigner,498    proxyContract.options.address,499    async (collection) => {500      const evmContract = await helper.ethNativeContract.collection(501        helper.ethAddress.fromCollectionId(collection.collectionId),502        'nft',503      );504505      await evmContract.methods.mintCross(506        helper.ethCrossAccount.fromAddress(receiverEthAddress),507        PROPERTIES.slice(0, setup.propertiesNumber),508      )509        .send({from: ethSigner});510    },511  );512513  const proxyContractFee = await calculateFeeNftMintWithProperties(514    helper,515    privateKey,516    {Ethereum: ethSigner},517    ethSigner,518    proxyContract.options.address,519    async (collection) => {520      await proxyContract.methods521        .mintToSubstrateWithProperty(522          helper.ethAddress.fromCollectionId(collection.collectionId),523          susbstrateReceiver.addressRaw,524          PROPERTIES.slice(0, setup.propertiesNumber),525        )526        .send({from: ethSigner});527    },528  );529530  const proxyContractBulkFee = await calculateFeeNftMintWithProperties(531    helper,532    privateKey,533    {Ethereum: ethSigner},534    ethSigner,535    proxyContract.options.address,536    async (collection) => {537      await proxyContract.methods538        .mintToSubstrateBulkProperty(539          helper.ethAddress.fromCollectionId(collection.collectionId),540          susbstrateReceiver.addressRaw,541          PROPERTIES.slice(0, setup.propertiesNumber),542        )543        .send({from: ethSigner, gas: 25_000_000});544    },545  );546547  return {548    propertiesNumber: setup.propertiesNumber,549    substrateFee: convertToTokens(substrateFee, nominal),550    ethFee: convertToTokens(ethFee, nominal),551    ethBulkFee: convertToTokens(ethBulkFee, nominal),552    ethMintCrossFee: convertToTokens(ethMintCrossFee, nominal),553    evmProxyContractFee: convertToTokens(proxyContractFee, nominal),554    evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),555  };556}557558async function calculateFeeNftMintWithProperties(559  helper: EthUniqueHelper,560  privateKey: (seed: string) => Promise<IKeyringPair>,561  payer: ICrossAccountId,562  ethSigner: string,563  proxyContractAddress: string,564  calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,565): Promise<bigint> {566  const collection = await createCollectionForBenchmarks(567    helper,568    privateKey,569    ethSigner,570    proxyContractAddress,571    PERMISSIONS,572  );573  return helper.arrange.calculcateFee(payer, async () => {574    await calculatedCall(collection);575  });576}577function convertToTokens(value: bigint, nominal: bigint): number {578  return Number((value * 1000n) / nominal) / 1000;579}
after · tests/src/benchmarks/mintFee/benchmark.ts
1import {EthUniqueHelper, SponsoringMode, usingEthPlaygrounds} from '../../eth/util';2import {readFile} from 'fs/promises';3import {CollectionLimitField, ContractImports, TokenPermissionField} from '../../eth/util/playgrounds/types';4import {5  ICrossAccountId,6  ITokenPropertyPermission,7} from '../../util/playgrounds/types';8import {IKeyringPair} from '@polkadot/types/types';9import {UniqueNFTCollection} from '../../util/playgrounds/unique';10import {Contract} from 'web3-eth-contract';11import {createObjectCsvWriter} from 'csv-writer';12import {FeeGas} from '../../eth/util/playgrounds/unique.dev';1314const CONTRACT_IMPORT: ContractImports[] = [15  {16    fsPath: `${__dirname}/../../eth/api/CollectionHelpers.sol`,17    solPath: 'eth/api/CollectionHelpers.sol',18  },19  {20    fsPath: `${__dirname}/../../eth/api/ContractHelpers.sol`,21    solPath: 'eth/api/ContractHelpers.sol',22  },23  {24    fsPath: `${__dirname}/../../eth/api/UniqueRefungibleToken.sol`,25    solPath: 'eth/api/UniqueRefungibleToken.sol',26  },27  {28    fsPath: `${__dirname}/../../eth/api/UniqueRefungible.sol`,29    solPath: 'eth/api/UniqueRefungible.sol',30  },31  {32    fsPath: `${__dirname}/../../eth/api/UniqueNFT.sol`,33    solPath: 'eth/api/UniqueNFT.sol',34  },35];3637const PROPERTIES = Array(40)38  .fill(0)39  .map((_, i) => {40    return {41      key: `key_${i}`,42      value: Uint8Array.from(Buffer.from(`value_${i}`)),43    };44  });4546const PERMISSIONS: ITokenPropertyPermission[] = PROPERTIES.map((p) => {47  return {48    key: p.key,49    permission: {50      tokenOwner: true,51      collectionAdmin: true,52      mutable: true,53    },54  };55});5657interface IBenchmarkResultForProp {58	propertiesNumber: number;59	substrateFee: number;60	ethFee: number;61	ethBulkFee: number;62  ethMintCrossFee: number;63	evmProxyContractFee: number;64	evmProxyContractBulkFee: number;65}66interface IBenchmarkCollection {67  createFee: number,68  mintFee: number,69  transferFee: number,70}71const main = async () => {72  const benchmarks = [73    'substrateFee',74    'ethFee',75    'ethBulkFee',76    'ethMintCrossFee',77    'evmProxyContractFee',78    'evmProxyContractBulkFee',79  ];80  const headers = [81    'propertiesNumber',82    ...benchmarks,83  ];848586  const csvWriter = createObjectCsvWriter({87    path: 'properties.csv',88    header: headers,89  });9091  await usingEthPlaygrounds(async (helper, privateKey) => {92    const NOMINAL = helper.balance.getOneTokenNominal();93    const CONTRACT_SOURCE = (94      await readFile(`${__dirname}/proxyContract.sol`)95    ).toString();96    const ZEPPELIN_OBJECT = '0x' + (await readFile(`${__dirname}/openZeppelin/bin/ZeppelinContract.bin`)).toString();97    const ZEPPELIN_ABI = JSON.parse((await readFile(`${__dirname}/openZeppelin/bin/ZeppelinContract.abi`)).toString());9899    console.log('\n ERC721 ops fees');100    console.table(await ERC721CalculateFeeGas(helper, privateKey), ['fee', 'gas', 'substrate']);101102    const donor = await privateKey('//Alice'); // Seed from account with balance on this network103    const [substrateReceiver] = await helper.arrange.createAccounts([10n], donor);104105    const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);106    const ethReceiver = await helper.eth.createAccountWithBalance(donor, 5n);107108    let susbtrateCollection: UniqueNFTCollection | null;109    const createCollectionSubstrateFee = convertToTokens(110      await helper.arrange.calculcateFee({Substrate: donor.address}, async () => {111        susbtrateCollection = await helper.nft.mintCollection(donor, {tokenPropertyPermissions: [112          {113            key: 'url',114            permission: {115              tokenOwner: true,116              collectionAdmin: true,117              mutable: true,118            },119          },120        ]});121      }),122      NOMINAL,123    );124125    const susbstrateMintFee = convertToTokens(126      await helper.arrange.calculcateFee(127        {Substrate: donor.address},128        () => susbtrateCollection!.mintToken(donor, {Substrate: substrateReceiver.address}, [{key: 'url', value: 'test'}]),129      ),130      NOMINAL,131    );132    const susbstrateTransferFee = convertToTokens(133      await helper.arrange.calculcateFee(134        {Substrate: substrateReceiver.address},135        () => susbtrateCollection!.transferToken(substrateReceiver, 1, {Substrate: donor.address}),136      ),137      NOMINAL,138    );139    const substrateFee: IBenchmarkCollection = {140      createFee: createCollectionSubstrateFee,141      mintFee: susbstrateMintFee,142      transferFee: susbstrateTransferFee,143    };144145    const helperContract = await helper.ethNativeContract.collectionHelpers(ethSigner);146147    let ethContract: Contract | null = null;148149    const createCollectionEthFee = convertToTokens(150      await helper.arrange.calculcateFee({Ethereum: ethSigner}, async () => {151        const result = await helperContract.methods.createNFTCollection('a', 'b', 'c').send({from: ethSigner, value: Number(2n * helper.balance.getOneTokenNominal())});152        ethContract = await helper.ethNativeContract.collection(helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId), 'nft', ethSigner);153      }),154      NOMINAL,155    );156157    await ethContract!.methods.setTokenPropertyPermissions([158      ['url', [159        [TokenPermissionField.Mutable, true],160        [TokenPermissionField.TokenOwner, true],161        [TokenPermissionField.CollectionAdmin, true]],162      ],163    ]).send({from: ethSigner});164165    const ethMintFee = convertToTokens(await helper.arrange.calculcateFee(166      {Ethereum: ethSigner},167      () => ethContract!.methods.mintCross(168        helper.ethCrossAccount.fromAddress(ethReceiver),169        [{key: 'url', value: Buffer.from('test')}],170      )171        .send({from: ethSigner}),172    ), NOMINAL);173174    const ethTransferFee = convertToTokens(await helper.arrange.calculcateFee(175      {Ethereum: ethReceiver},176      () => ethContract!.methods.transferFrom(ethReceiver, ethSigner, 1)177        .send({from: ethReceiver}),178    ), NOMINAL);179180    const ethFee: IBenchmarkCollection = {181      createFee: createCollectionEthFee,182      mintFee: ethMintFee,183      transferFee: ethTransferFee,184    };185186    let zeppelelinContract: Contract | null = null;187188    const zeppelinDeployFee = convertToTokens(189      await helper.arrange.calculcateFee({Ethereum: ethSigner}, async () => {190        zeppelelinContract = await helper.ethContract.deployByAbi(191          ethSigner,192          ZEPPELIN_ABI,193          ZEPPELIN_OBJECT,194        );195      }),196      NOMINAL,197    );198199    const zeppelinMintFee = convertToTokens(await helper.arrange.calculcateFee(200      {Ethereum: ethSigner},201      () => zeppelelinContract!.methods.safeMint(ethReceiver, 'test').send({from: ethSigner}),202    ), NOMINAL);203204205    const zeppelinTransferFee = convertToTokens(await helper.arrange.calculcateFee(206      {Ethereum: ethReceiver},207      () => zeppelelinContract!.methods.safeTransferFrom(ethReceiver, ethSigner, 0).send({from: ethReceiver}),208    ), NOMINAL);209210    const zeppelinFee: IBenchmarkCollection = {211      createFee: zeppelinDeployFee,212      mintFee: zeppelinMintFee,213      transferFee: zeppelinTransferFee,214    };215216    console.log('collection ops fees');217    const results = {substrate: substrateFee, eth: ethFee, zeppelin: zeppelinFee};218    console.table(results);219220    const contract = await helper.ethContract.deployByCode(221      ethSigner,222      'ProxyMint',223      CONTRACT_SOURCE,224      CONTRACT_IMPORT,225    );226227    const fees = await benchMintFee(helper, privateKey, contract);228    console.log('Minting without properties');229    console.table(fees);230231    const result: IBenchmarkResultForProp[] = [];232    const csvResult: IBenchmarkResultForProp[] = [];233234    for (let i = 1; i <= 20; i++) {235      const benchResult = await benchMintWithProperties(helper, privateKey, contract, {236        propertiesNumber: i,237      }) as any;238239      csvResult.push(benchResult);240241      const minFee = Math.min(...(benchmarks.map(x => benchResult[x])));242      for(const key of benchmarks) {243        const keyPercent = Math.round((benchResult[key] / minFee) * 100);244        benchResult[key] = `${benchResult[key]} (${keyPercent}%)`;245      }246247      result.push(benchResult);248    }249250    await csvWriter.writeRecords(csvResult);251252    console.log('Minting with properties');253    console.table(result, headers);254  });255};256257main()258  .then(() => process.exit(0))259  .catch((e) => {260    console.log(e);261    process.exit(1);262  });263264async function createCollectionForBenchmarks(265  helper: EthUniqueHelper,266  privateKey: (seed: string) => Promise<IKeyringPair>,267  ethSigner: string,268  proxyContract: string | null,269  permissions: ITokenPropertyPermission[],270) {271  const donor = await privateKey('//Alice');272273  const collection = await helper.nft.mintCollection(donor, {274    name: 'test mintToSubstrate',275    description: 'EVMHelpers',276    tokenPrefix: 'ap',277    tokenPropertyPermissions: [278      {279        key: 'url',280        permission: {281          tokenOwner: true,282          collectionAdmin: true,283          mutable: true,284        },285      },286      {287        key: 'URI',288        permission: {289          tokenOwner: true,290          collectionAdmin: true,291          mutable: true,292        },293      },294    ],295    limits: {sponsorTransferTimeout: 0, sponsorApproveTimeout: 0},296    permissions: {mintMode: true},297  });298299  await collection.addToAllowList(donor, {300    Ethereum: helper.address.substrateToEth(donor.address),301  });302  await collection.addToAllowList(donor, {Substrate: donor.address});303  await collection.addAdmin(donor, {Ethereum: ethSigner});304  await collection.addAdmin(donor, {305    Ethereum: helper.address.substrateToEth(donor.address),306  });307308  if (proxyContract) {309    await collection.addToAllowList(donor, {Ethereum: proxyContract});310    await collection.addAdmin(donor, {Ethereum: proxyContract});311  }312313  await collection.setTokenPropertyPermissions(donor, permissions);314315  return collection;316}317318async function benchMintFee(319  helper: EthUniqueHelper,320  privateKey: (seed: string) => Promise<IKeyringPair>,321  proxyContract: Contract,322): Promise<{323	substrateFee: number;324	ethFee: number;325	evmProxyContractFee: number;326}> {327  const donor = await privateKey('//Alice');328  const substrateReceiver = await privateKey('//Bob');329  const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);330331  const nominal = helper.balance.getOneTokenNominal();332333  await helper.eth.transferBalanceFromSubstrate(334    donor,335    proxyContract.options.address,336    100n,337  );338339  const collection = await createCollectionForBenchmarks(340    helper,341    privateKey,342    ethSigner,343    proxyContract.options.address,344    PERMISSIONS,345  );346347  const substrateFee = await helper.arrange.calculcateFee(348    {Substrate: donor.address},349    () => collection.mintToken(donor, {Substrate: substrateReceiver.address}),350  );351352  const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId);353  const collectionContract = await helper.ethNativeContract.collection(354    collectionEthAddress,355    'nft',356  );357358  const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address);359360  const encodedCall = collectionContract.methods361    .mint(receiverEthAddress)362    .encodeABI();363364  const ethFee = await helper.arrange.calculcateFee(365    {Substrate: donor.address},366    async () => {367      await helper.eth.sendEVM(368        donor,369        collectionContract.options.address,370        encodedCall,371        '0',372      );373    },374  );375376  const evmProxyContractFee = await helper.arrange.calculcateFee(377    {Ethereum: ethSigner},378    async () => {379      await proxyContract.methods380        .mintToSubstrate(381          helper.ethAddress.fromCollectionId(collection.collectionId),382          substrateReceiver.addressRaw,383        )384        .send({from: ethSigner});385    },386  );387388  return {389    substrateFee: convertToTokens(substrateFee, nominal),390    ethFee: convertToTokens(ethFee, nominal),391    evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal),392  };393}394395async function benchMintWithProperties(396  helper: EthUniqueHelper,397  privateKey: (seed: string) => Promise<IKeyringPair>,398  proxyContract: Contract,399  setup: { propertiesNumber: number },400): Promise<IBenchmarkResultForProp> {401  const donor = await privateKey('//Alice'); // Seed from account with balance on this network402  const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);403404  const susbstrateReceiver = await privateKey('//Bob');405  const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address);406407  const nominal = helper.balance.getOneTokenNominal();408409  const substrateFee = await calculateFeeNftMintWithProperties(410    helper,411    privateKey,412    {Substrate: donor.address},413    ethSigner,414    proxyContract.options.address,415    async (collection) => {416      await collection.mintToken(417        donor,418        {Substrate: susbstrateReceiver.address},419        PROPERTIES.slice(0, setup.propertiesNumber).map((p) => {420          return {key: p.key, value: Buffer.from(p.value).toString()};421        }),422      );423    },424  );425426  const ethFee = await calculateFeeNftMintWithProperties(427    helper,428    privateKey,429    {Substrate: donor.address},430    ethSigner,431    proxyContract.options.address,432    async (collection) => {433      const evmContract = await helper.ethNativeContract.collection(434        helper.ethAddress.fromCollectionId(collection.collectionId),435        'nft',436        undefined,437        true,438      );439440      const subTokenId = await evmContract.methods.nextTokenId().call();441442      let encodedCall = evmContract.methods443        .mint(receiverEthAddress)444        .encodeABI();445446      await helper.eth.sendEVM(447        donor,448        evmContract.options.address,449        encodedCall,450        '0',451      );452453      for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) {454        encodedCall = await evmContract.methods455          .setProperty(subTokenId, val.key, Buffer.from(val.value))456          .encodeABI();457458        await helper.eth.sendEVM(459          donor,460          evmContract.options.address,461          encodedCall,462          '0',463        );464      }465    },466  );467468  const ethBulkFee = await calculateFeeNftMintWithProperties(469    helper,470    privateKey,471    {Substrate: donor.address},472    ethSigner,473    proxyContract.options.address,474    async (collection) => {475      const evmContract = await helper.ethNativeContract.collection(476        helper.ethAddress.fromCollectionId(collection.collectionId),477        'nft',478      );479480      const subTokenId = await evmContract.methods.nextTokenId().call();481482      let encodedCall = evmContract.methods483        .mint(receiverEthAddress)484        .encodeABI();485486      await helper.eth.sendEVM(487        donor,488        evmContract.options.address,489        encodedCall,490        '0',491      );492493      encodedCall = await evmContract.methods494        .setProperties(495          subTokenId,496          PROPERTIES.slice(0, setup.propertiesNumber),497        )498        .encodeABI();499500      await helper.eth.sendEVM(501        donor,502        evmContract.options.address,503        encodedCall,504        '0',505      );506    },507  );508509  const ethMintCrossFee = await calculateFeeNftMintWithProperties(510    helper,511    privateKey,512    {Ethereum: ethSigner},513    ethSigner,514    proxyContract.options.address,515    async (collection) => {516      const evmContract = await helper.ethNativeContract.collection(517        helper.ethAddress.fromCollectionId(collection.collectionId),518        'nft',519      );520521      await evmContract.methods.mintCross(522        helper.ethCrossAccount.fromAddress(receiverEthAddress),523        PROPERTIES.slice(0, setup.propertiesNumber),524      )525        .send({from: ethSigner});526    },527  );528529  const proxyContractFee = await calculateFeeNftMintWithProperties(530    helper,531    privateKey,532    {Ethereum: ethSigner},533    ethSigner,534    proxyContract.options.address,535    async (collection) => {536      await proxyContract.methods537        .mintToSubstrateWithProperty(538          helper.ethAddress.fromCollectionId(collection.collectionId),539          susbstrateReceiver.addressRaw,540          PROPERTIES.slice(0, setup.propertiesNumber),541        )542        .send({from: ethSigner});543    },544  );545546  const proxyContractBulkFee = await calculateFeeNftMintWithProperties(547    helper,548    privateKey,549    {Ethereum: ethSigner},550    ethSigner,551    proxyContract.options.address,552    async (collection) => {553      await proxyContract.methods554        .mintToSubstrateBulkProperty(555          helper.ethAddress.fromCollectionId(collection.collectionId),556          susbstrateReceiver.addressRaw,557          PROPERTIES.slice(0, setup.propertiesNumber),558        )559        .send({from: ethSigner, gas: 25_000_000});560    },561  );562563  return {564    propertiesNumber: setup.propertiesNumber,565    substrateFee: convertToTokens(substrateFee, nominal),566    ethFee: convertToTokens(ethFee, nominal),567    ethBulkFee: convertToTokens(ethBulkFee, nominal),568    ethMintCrossFee: convertToTokens(ethMintCrossFee, nominal),569    evmProxyContractFee: convertToTokens(proxyContractFee, nominal),570    evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),571  };572}573574async function calculateFeeNftMintWithProperties(575  helper: EthUniqueHelper,576  privateKey: (seed: string) => Promise<IKeyringPair>,577  payer: ICrossAccountId,578  ethSigner: string,579  proxyContractAddress: string,580  calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,581): Promise<bigint> {582  const collection = await createCollectionForBenchmarks(583    helper,584    privateKey,585    ethSigner,586    proxyContractAddress,587    PERMISSIONS,588  );589  return helper.arrange.calculcateFee(payer, async () => {590    await calculatedCall(collection);591  });592}593function convertToTokens(value: bigint, nominal: bigint): number {594  return Number((value * 1000n) / nominal) / 1000;595}596597interface IFeeGas {598  fee: number | bigint,599  gas: number | bigint,600  substrate?: number,601}602interface IFunctionFee {603  [name: string]: IFeeGas604}605606async function ERC721CalculateFeeGas(607  helper: EthUniqueHelper,608  privateKey: (seed: string) => Promise<IKeyringPair>,609610): Promise<IFunctionFee> {611  const res: IFunctionFee = {};612  const donor = await privateKey('//Alice');613  const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);614  const ethReceiver = await helper.eth.createAccountWithBalance(donor, 10n);615  const crossSigner = helper.ethCrossAccount.fromAddress(ethSigner);616  const crossReceiver = helper.ethCrossAccount.fromAddress(ethReceiver);617  const collection = await createCollectionForBenchmarks(618    helper,619    privateKey,620    ethSigner,621    null,622    PERMISSIONS,623  );624625  const evmContract = await helper.ethNativeContract.collection(626    helper.ethAddress.fromCollectionId(collection.collectionId),627    'nft',628    ethSigner,629    true,630  );631632  res['mint'] =633    await helper.arrange.calculcateFeeGas(634      {Ethereum: ethSigner},635      () => evmContract.methods.mint(ethSigner).send(),636    );637638  res['mintCross'] =639    await helper.arrange.calculcateFeeGas(640      {Ethereum: ethSigner},641      () => evmContract.methods.mintCross(crossSigner, []).send(),642    );643644  res['mintWithTokenURI'] =645    await helper.arrange.calculcateFeeGas(646      {Ethereum: ethSigner},647      () => evmContract.methods.mintWithTokenURI(ethSigner, 'Test URI').send(),648    );649650  res['setProperties'] =651    await helper.arrange.calculcateFeeGas(652      {Ethereum: ethSigner},653      () => evmContract.methods.setProperties(1, PROPERTIES.slice(0,1)).send(),654    );655656  res['deleteProperties'] =657    await helper.arrange.calculcateFeeGas(658      {Ethereum: ethSigner},659      () => evmContract.methods.deleteProperties(1, [PROPERTIES[0].key]).send(),660    );661662  res['transfer'] =663    await helper.arrange.calculcateFeeGas(664      {Ethereum: ethSigner},665      () => evmContract.methods.transfer(ethReceiver, 1).send(),666    );667668  res['transferCross'] =669    await helper.arrange.calculcateFeeGas(670      {Ethereum: ethReceiver},671      () => evmContract.methods.transferCross(crossSigner, 1).send({from: ethReceiver}),672    );673674  res['transferFrom'] =675    await helper.arrange.calculcateFeeGas(676      {Ethereum: ethSigner},677      () => evmContract.methods.transferFrom(ethSigner, ethReceiver, 1).send(),678    );679680  res['transferFromCross'] =681    await helper.arrange.calculcateFeeGas(682      {Ethereum: ethReceiver},683      () => evmContract.methods.transferFromCross(crossReceiver, crossSigner, 1).send({from:ethReceiver}),684    );685686  res['burn'] =687    await helper.arrange.calculcateFeeGas(688      {Ethereum: ethSigner},689      () => evmContract.methods.burn(1).send(),690    );691692  res['approveCross'] =693    await helper.arrange.calculcateFeeGas(694      {Ethereum: ethSigner},695      () => evmContract.methods.approveCross(crossReceiver, 2).send(),696    );697698  res['burnFromCross'] =699    await helper.arrange.calculcateFeeGas(700      {Ethereum: ethReceiver},701      () => evmContract.methods.burnFromCross(crossSigner, 2).send({from:ethReceiver}),702    );703704  res['setTokenPropertyPermissions'] =705    await helper.arrange.calculcateFeeGas(706      {Ethereum: ethSigner},707      () =>  evmContract.methods.setTokenPropertyPermissions([708        ['url', [709          [TokenPermissionField.Mutable, true],710          [TokenPermissionField.TokenOwner, true],711          [TokenPermissionField.CollectionAdmin, true]],712        ],713      ]).send(),714    );715716  res['setCollectionSponsorCross'] =717    await helper.arrange.calculcateFeeGas(718      {Ethereum: ethSigner},719      () =>  evmContract.methods.setCollectionSponsorCross(crossReceiver).send(),720    );721722  res['confirmCollectionSponsorship'] =723    await helper.arrange.calculcateFeeGas(724      {Ethereum: ethReceiver},725      () =>  evmContract.methods.confirmCollectionSponsorship().send({from: ethReceiver}),726    );727728  res['removeCollectionSponsor'] =729    await helper.arrange.calculcateFeeGas(730      {Ethereum: ethSigner},731      () =>  evmContract.methods.removeCollectionSponsor().send(),732    );733734  res['setCollectionProperties'] =735    await helper.arrange.calculcateFeeGas(736      {Ethereum: ethSigner},737      () => evmContract.methods.setCollectionProperties(PROPERTIES.slice(0, 1)).send(),738    );739740741742  res['deleteCollectionProperties'] =743    await helper.arrange.calculcateFeeGas(744      {Ethereum: ethSigner},745      () =>  evmContract.methods.deleteCollectionProperties(PROPERTIES.slice(0,1).map(p => p.key)).send(),746    );747748  res['setCollectionLimit'] =749    await helper.arrange.calculcateFeeGas(750      {Ethereum: ethSigner},751      () =>  evmContract.methods.setCollectionLimit({field: CollectionLimitField.AccountTokenOwnership, value: {status: true, value: 1000}}).send(),752    );753754  const {collectionAddress} = await helper.eth.createCollection('nft', ethSigner, 'A', 'B', 'C');755  const collectionWithEthOwner = await helper.ethNativeContract.collection(collectionAddress, 'nft', ethSigner, true);756757758  res['addCollectionAdminCross'] =759    await helper.arrange.calculcateFeeGas(760      {Ethereum: ethSigner},761      () =>  collectionWithEthOwner.methods.addCollectionAdminCross(crossReceiver).send(),762    );763764  res['removeCollectionAdminCross'] =765    await helper.arrange.calculcateFeeGas(766      {Ethereum: ethSigner},767      () =>  collectionWithEthOwner.methods.removeCollectionAdminCross(crossReceiver).send(),768    );769770  res['setCollectionNesting'] =771    await helper.arrange.calculcateFeeGas(772      {Ethereum: ethSigner},773      () =>  evmContract.methods.setCollectionNesting(true).send(),774    );775776  res['setCollectionNesting[]'] =777    await helper.arrange.calculcateFeeGas(778      {Ethereum: ethSigner},779      () =>  evmContract.methods.setCollectionNesting(true, [collectionAddress]).send(),780    );781782  res['setCollectionAcces'] =783    await helper.arrange.calculcateFeeGas(784      {Ethereum: ethSigner},785      () =>  evmContract.methods.setCollectionAccess(SponsoringMode.Allowlisted).send(),786    );787788  res['addToCollectionAllowListCross'] =789    await helper.arrange.calculcateFeeGas(790      {Ethereum: ethSigner},791      () =>  evmContract.methods.addToCollectionAllowListCross(crossReceiver).send(),792    );793794  res['removeFromCollectionAllowListCross'] =795    await helper.arrange.calculcateFeeGas(796      {Ethereum: ethSigner},797      () =>  evmContract.methods.removeFromCollectionAllowListCross(crossReceiver).send(),798    );799800  res['setCollectionMintMode'] =801    await helper.arrange.calculcateFeeGas(802      {Ethereum: ethSigner},803      () =>  evmContract.methods.setCollectionMintMode(true).send(),804    );805806  res['changeCollectionOwnerCross'] =807    await helper.arrange.calculcateFeeGas(808      {Ethereum: ethSigner},809      () =>  collectionWithEthOwner.methods.changeCollectionOwnerCross(crossReceiver).send(),810    );811  for (const name in res) {812    res[name] = {fee: res[name].fee, gas: res[name].gas};813  }814  return res;815}
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -16,7 +16,7 @@
 import {evmToAddress} from '@polkadot/util-crypto';
 import {IKeyringPair} from '@polkadot/types/types';
 
-import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
+import {ArrangeGroup, DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
 
 import {ContractImports, CompiledContract, CrossAddress, NormalizedEvent, EthProperty} from './types';
 
@@ -461,6 +461,41 @@
   }
 }
 
+export class FeeGas {
+  fee: number | bigint = 0n;
+
+  gas: number | bigint = 0n;
+
+  public static async build(helper: EthUniqueHelper, fee: bigint): Promise<FeeGas> {
+    const instance = new FeeGas();
+    instance.fee = instance.convertToTokens(fee);
+    instance.gas = await instance.convertToGas(fee, helper);
+    return instance;
+  }
+
+  private async convertToGas(fee: bigint, helper: EthUniqueHelper): Promise<bigint> {
+    const gasPrice = BigInt(await helper.getWeb3().eth.getGasPrice());
+    return fee / gasPrice;
+  }
+
+  private convertToTokens(value: bigint, nominal = 1_000_000_000_000_000_000n): number {
+    return Number((value * 1000n) / nominal) / 1000;
+  }
+}
+
+class EthArrangeGroup extends ArrangeGroup {
+  helper: EthUniqueHelper;
+
+  constructor(helper: EthUniqueHelper) {
+    super(helper);
+    this.helper = helper;
+  }
+
+  async calculcateFeeGas(payer: ICrossAccountId, promise: () => Promise<any>): Promise<FeeGas> {
+    const fee = await this.calculcateFee(payer, promise);
+    return await FeeGas.build(this.helper, fee);
+  }
+}
 export class EthUniqueHelper extends DevUniqueHelper {
   web3: Web3 | null = null;
   web3Provider: WebsocketProvider | null = null;
@@ -471,7 +506,7 @@
   ethNativeContract: NativeContractGroup;
   ethContract: ContractGroup;
   ethProperty: EthPropertyGroup;
-
+  arrange: EthArrangeGroup;
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? EthUniqueHelper;
 
@@ -482,6 +517,8 @@
     this.ethNativeContract = new NativeContractGroup(this);
     this.ethContract = new ContractGroup(this);
     this.ethProperty = new EthPropertyGroup(this);
+    this.arrange = new EthArrangeGroup(this);
+    super.arrange = this.arrange;
   }
 
   getWeb3(): Web3 {
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -187,7 +187,7 @@
 
 export class DevKaruraHelper extends DevAcalaHelper {}
 
-class ArrangeGroup {
+export class ArrangeGroup {
   helper: DevUniqueHelper;
 
   scheduledIdSlider = 0;