1import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util';2import {readFile} from 'fs/promises';3import {4 ICrossAccountId,5} from '../../util/playgrounds/types';6import {IKeyringPair} from '@polkadot/types/types';7import {UniqueNFTCollection} from '../../util/playgrounds/unique';8import {Contract} from 'web3-eth-contract';9import {createObjectCsvWriter} from 'csv-writer';10import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common';11import {makeNames} from '../../util';12import {ContractImports} from '../../eth/util/playgrounds/types';1314const {dirname} = makeNames(import.meta.url);1516export const CONTRACT_IMPORT: ContractImports[] = [17 {18 fsPath: `${dirname}/../../eth/api/CollectionHelpers.sol`,19 solPath: 'eth/api/CollectionHelpers.sol',20 },21 {22 fsPath: `${dirname}/../../eth/api/ContractHelpers.sol`,23 solPath: 'eth/api/ContractHelpers.sol',24 },25 {26 fsPath: `${dirname}/../../eth/api/UniqueRefungibleToken.sol`,27 solPath: 'eth/api/UniqueRefungibleToken.sol',28 },29 {30 fsPath: `${dirname}/../../eth/api/UniqueRefungible.sol`,31 solPath: 'eth/api/UniqueRefungible.sol',32 },33 {34 fsPath: `${dirname}/../../eth/api/UniqueNFT.sol`,35 solPath: 'eth/api/UniqueNFT.sol',36 },37];3839interface IBenchmarkResultForProp {40 propertiesNumber: number;41 substrateFee: number;42 ethFee: number;43 ethBulkFee: number;44 ethMintCrossFee: number;45 evmProxyContractFee: number;46 evmProxyContractBulkFee: number;47}4849const main = async () => {50 const benchmarks = [51 'substrateFee',52 'ethFee',53 'ethBulkFee',54 'ethMintCrossFee',55 'evmProxyContractFee',56 'evmProxyContractBulkFee',57 ];58 const headers = [59 'propertiesNumber',60 ...benchmarks,61 ];626364 const csvWriter = createObjectCsvWriter({65 path: 'properties.csv',66 header: headers,67 });6869 await usingEthPlaygrounds(async (helper, privateKey) => {70 const CONTRACT_SOURCE = (71 await readFile(`${dirname}/proxyContract.sol`)72 ).toString();7374 const donor = await privateKey('//Alice'); 75 const ethSigner = await helper.eth.createAccountWithBalance(donor);7677 const contract = await helper.ethContract.deployByCode(78 ethSigner,79 'ProxyMint',80 CONTRACT_SOURCE,81 CONTRACT_IMPORT,82 );8384 const fees = await benchMintFee(helper, privateKey, contract);85 console.log('Minting without properties');86 console.table(fees);8788 const result: IBenchmarkResultForProp[] = [];89 const csvResult: IBenchmarkResultForProp[] = [];9091 for(let i = 1; i <= 20; i++) {92 const benchResult = await benchMintWithProperties(helper, privateKey, contract, {93 propertiesNumber: i,94 }) as any;9596 csvResult.push(benchResult);9798 const minFee = Math.min(...(benchmarks.map(x => benchResult[x])));99 for(const key of benchmarks) {100 const keyPercent = Math.round((benchResult[key] / minFee) * 100);101 benchResult[key] = `${benchResult[key]} (${keyPercent}%)`;102 }103104 result.push(benchResult);105 }106107 await csvWriter.writeRecords(csvResult);108109 console.log('Minting with properties');110 console.table(result, headers);111 });112};113114main()115 .then(() => process.exit(0))116 .catch((e) => {117 console.log(e);118 process.exit(1);119 });120121122123async function benchMintFee(124 helper: EthUniqueHelper,125 privateKey: (seed: string) => Promise<IKeyringPair>,126 proxyContract: Contract,127): Promise<{128 substrateFee: number;129 ethFee: number;130 evmProxyContractFee: number;131}> {132 const donor = await privateKey('//Alice');133 const substrateReceiver = await privateKey('//Bob');134 const ethSigner = await helper.eth.createAccountWithBalance(donor);135136 const nominal = helper.balance.getOneTokenNominal();137138 await helper.eth.transferBalanceFromSubstrate(139 donor,140 proxyContract.options.address,141 100n,142 );143144 const collection = (await createCollectionForBenchmarks(145 'nft',146 helper,147 privateKey,148 ethSigner,149 proxyContract.options.address,150 PERMISSIONS,151 )) as UniqueNFTCollection;152153 const substrateFee = await helper.arrange.calculcateFee(154 {Substrate: donor.address},155 () => collection.mintToken(donor, {Substrate: substrateReceiver.address}),156 );157158 const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId);159 const collectionContract = await helper.ethNativeContract.collection(160 collectionEthAddress,161 'nft',162 );163164 const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address);165166 const encodedCall = collectionContract.methods167 .mint(receiverEthAddress)168 .encodeABI();169170 const ethFee = await helper.arrange.calculcateFee(171 {Substrate: donor.address},172 async () => {173 await helper.eth.sendEVM(174 donor,175 collectionContract.options.address,176 encodedCall,177 '0',178 );179 },180 );181182 const evmProxyContractFee = await helper.arrange.calculcateFee(183 {Ethereum: ethSigner},184 async () => {185 await proxyContract.methods186 .mintToSubstrate(187 helper.ethAddress.fromCollectionId(collection.collectionId),188 substrateReceiver.addressRaw,189 )190 .send({from: ethSigner});191 },192 );193194 return {195 substrateFee: convertToTokens(substrateFee, nominal),196 ethFee: convertToTokens(ethFee, nominal),197 evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal),198 };199}200201async function benchMintWithProperties(202 helper: EthUniqueHelper,203 privateKey: (seed: string) => Promise<IKeyringPair>,204 proxyContract: Contract,205 setup: { propertiesNumber: number },206): Promise<IBenchmarkResultForProp> {207 const donor = await privateKey('//Alice'); 208 const ethSigner = await helper.eth.createAccountWithBalance(donor);209210 const susbstrateReceiver = await privateKey('//Bob');211 const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address);212213 const nominal = helper.balance.getOneTokenNominal();214215 const substrateFee = await calculateFeeNftMintWithProperties(216 helper,217 privateKey,218 {Substrate: donor.address},219 ethSigner,220 proxyContract.options.address,221 async (collection) => {222 await collection.mintToken(223 donor,224 {Substrate: susbstrateReceiver.address},225 PROPERTIES.slice(0, setup.propertiesNumber).map((p) => ({key: p.key, value: Buffer.from(p.value).toString()})),226 );227 },228 );229230 const ethFee = await calculateFeeNftMintWithProperties(231 helper,232 privateKey,233 {Substrate: donor.address},234 ethSigner,235 proxyContract.options.address,236 async (collection) => {237 const evmContract = await helper.ethNativeContract.collection(238 helper.ethAddress.fromCollectionId(collection.collectionId),239 'nft',240 undefined,241 true,242 );243244 const subTokenId = await evmContract.methods.nextTokenId().call();245246 let encodedCall = evmContract.methods247 .mint(receiverEthAddress)248 .encodeABI();249250 await helper.eth.sendEVM(251 donor,252 evmContract.options.address,253 encodedCall,254 '0',255 );256257 for(const val of PROPERTIES.slice(0, setup.propertiesNumber)) {258 encodedCall = await evmContract.methods259 .setProperty(subTokenId, val.key, Buffer.from(val.value))260 .encodeABI();261262 await helper.eth.sendEVM(263 donor,264 evmContract.options.address,265 encodedCall,266 '0',267 );268 }269 },270 );271272 const ethBulkFee = await calculateFeeNftMintWithProperties(273 helper,274 privateKey,275 {Substrate: donor.address},276 ethSigner,277 proxyContract.options.address,278 async (collection) => {279 const evmContract = await helper.ethNativeContract.collection(280 helper.ethAddress.fromCollectionId(collection.collectionId),281 'nft',282 );283284 const subTokenId = await evmContract.methods.nextTokenId().call();285286 let encodedCall = evmContract.methods287 .mint(receiverEthAddress)288 .encodeABI();289290 await helper.eth.sendEVM(291 donor,292 evmContract.options.address,293 encodedCall,294 '0',295 );296297 encodedCall = await evmContract.methods298 .setProperties(299 subTokenId,300 PROPERTIES.slice(0, setup.propertiesNumber),301 )302 .encodeABI();303304 await helper.eth.sendEVM(305 donor,306 evmContract.options.address,307 encodedCall,308 '0',309 );310 },311 );312313 const ethMintCrossFee = await calculateFeeNftMintWithProperties(314 helper,315 privateKey,316 {Ethereum: ethSigner},317 ethSigner,318 proxyContract.options.address,319 async (collection) => {320 const evmContract = await helper.ethNativeContract.collection(321 helper.ethAddress.fromCollectionId(collection.collectionId),322 'nft',323 );324325 await evmContract.methods.mintCross(326 helper.ethCrossAccount.fromAddress(receiverEthAddress),327 PROPERTIES.slice(0, setup.propertiesNumber),328 )329 .send({from: ethSigner});330 },331 );332333 const proxyContractFee = await calculateFeeNftMintWithProperties(334 helper,335 privateKey,336 {Ethereum: ethSigner},337 ethSigner,338 proxyContract.options.address,339 async (collection) => {340 await proxyContract.methods341 .mintToSubstrateWithProperty(342 helper.ethAddress.fromCollectionId(collection.collectionId),343 susbstrateReceiver.addressRaw,344 PROPERTIES.slice(0, setup.propertiesNumber),345 )346 .send({from: ethSigner});347 },348 );349350 const proxyContractBulkFee = await calculateFeeNftMintWithProperties(351 helper,352 privateKey,353 {Ethereum: ethSigner},354 ethSigner,355 proxyContract.options.address,356 async (collection) => {357 await proxyContract.methods358 .mintToSubstrateBulkProperty(359 helper.ethAddress.fromCollectionId(collection.collectionId),360 susbstrateReceiver.addressRaw,361 PROPERTIES.slice(0, setup.propertiesNumber),362 )363 .send({from: ethSigner});364 },365 );366367 return {368 propertiesNumber: setup.propertiesNumber,369 substrateFee: convertToTokens(substrateFee, nominal),370 ethFee: convertToTokens(ethFee, nominal),371 ethBulkFee: convertToTokens(ethBulkFee, nominal),372 ethMintCrossFee: convertToTokens(ethMintCrossFee, nominal),373 evmProxyContractFee: convertToTokens(proxyContractFee, nominal),374 evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),375 };376}377378async function calculateFeeNftMintWithProperties(379 helper: EthUniqueHelper,380 privateKey: (seed: string) => Promise<IKeyringPair>,381 payer: ICrossAccountId,382 ethSigner: string,383 proxyContractAddress: string,384 calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,385): Promise<bigint> {386 const collection = (await createCollectionForBenchmarks(387 'nft',388 helper,389 privateKey,390 ethSigner,391 proxyContractAddress,392 PERMISSIONS,393 )) as UniqueNFTCollection;394 return helper.arrange.calculcateFee(payer, async () => {395 await calculatedCall(collection);396 });397}398399400