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, 100n);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, 100n);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, 100n);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) => {226 return {key: p.key, value: Buffer.from(p.value).toString()};227 }),228 );229 },230 );231232 const ethFee = await calculateFeeNftMintWithProperties(233 helper,234 privateKey,235 {Substrate: donor.address},236 ethSigner,237 proxyContract.options.address,238 async (collection) => {239 const evmContract = await helper.ethNativeContract.collection(240 helper.ethAddress.fromCollectionId(collection.collectionId),241 'nft',242 undefined,243 true,244 );245246 const subTokenId = await evmContract.methods.nextTokenId().call();247248 let encodedCall = evmContract.methods249 .mint(receiverEthAddress)250 .encodeABI();251252 await helper.eth.sendEVM(253 donor,254 evmContract.options.address,255 encodedCall,256 '0',257 );258259 for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) {260 encodedCall = await evmContract.methods261 .setProperty(subTokenId, val.key, Buffer.from(val.value))262 .encodeABI();263264 await helper.eth.sendEVM(265 donor,266 evmContract.options.address,267 encodedCall,268 '0',269 );270 }271 },272 );273274 const ethBulkFee = await calculateFeeNftMintWithProperties(275 helper,276 privateKey,277 {Substrate: donor.address},278 ethSigner,279 proxyContract.options.address,280 async (collection) => {281 const evmContract = await helper.ethNativeContract.collection(282 helper.ethAddress.fromCollectionId(collection.collectionId),283 'nft',284 );285286 const subTokenId = await evmContract.methods.nextTokenId().call();287288 let encodedCall = evmContract.methods289 .mint(receiverEthAddress)290 .encodeABI();291292 await helper.eth.sendEVM(293 donor,294 evmContract.options.address,295 encodedCall,296 '0',297 );298299 encodedCall = await evmContract.methods300 .setProperties(301 subTokenId,302 PROPERTIES.slice(0, setup.propertiesNumber),303 )304 .encodeABI();305306 await helper.eth.sendEVM(307 donor,308 evmContract.options.address,309 encodedCall,310 '0',311 );312 },313 );314315 const ethMintCrossFee = await calculateFeeNftMintWithProperties(316 helper,317 privateKey,318 {Ethereum: ethSigner},319 ethSigner,320 proxyContract.options.address,321 async (collection) => {322 const evmContract = await helper.ethNativeContract.collection(323 helper.ethAddress.fromCollectionId(collection.collectionId),324 'nft',325 );326327 await evmContract.methods.mintCross(328 helper.ethCrossAccount.fromAddress(receiverEthAddress),329 PROPERTIES.slice(0, setup.propertiesNumber),330 )331 .send({from: ethSigner});332 },333 );334335 const proxyContractFee = await calculateFeeNftMintWithProperties(336 helper,337 privateKey,338 {Ethereum: ethSigner},339 ethSigner,340 proxyContract.options.address,341 async (collection) => {342 await proxyContract.methods343 .mintToSubstrateWithProperty(344 helper.ethAddress.fromCollectionId(collection.collectionId),345 susbstrateReceiver.addressRaw,346 PROPERTIES.slice(0, setup.propertiesNumber),347 )348 .send({from: ethSigner});349 },350 );351352 const proxyContractBulkFee = await calculateFeeNftMintWithProperties(353 helper,354 privateKey,355 {Ethereum: ethSigner},356 ethSigner,357 proxyContract.options.address,358 async (collection) => {359 await proxyContract.methods360 .mintToSubstrateBulkProperty(361 helper.ethAddress.fromCollectionId(collection.collectionId),362 susbstrateReceiver.addressRaw,363 PROPERTIES.slice(0, setup.propertiesNumber),364 )365 .send({from: ethSigner, gas: 25_000_000});366 },367 );368369 return {370 propertiesNumber: setup.propertiesNumber,371 substrateFee: convertToTokens(substrateFee, nominal),372 ethFee: convertToTokens(ethFee, nominal),373 ethBulkFee: convertToTokens(ethBulkFee, nominal),374 ethMintCrossFee: convertToTokens(ethMintCrossFee, nominal),375 evmProxyContractFee: convertToTokens(proxyContractFee, nominal),376 evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),377 };378}379380async function calculateFeeNftMintWithProperties(381 helper: EthUniqueHelper,382 privateKey: (seed: string) => Promise<IKeyringPair>,383 payer: ICrossAccountId,384 ethSigner: string,385 proxyContractAddress: string,386 calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,387): Promise<bigint> {388 const collection = (await createCollectionForBenchmarks(389 'nft',390 helper,391 privateKey,392 ethSigner,393 proxyContractAddress,394 PERMISSIONS,395 )) as UniqueNFTCollection;396 return helper.arrange.calculcateFee(payer, async () => {397 await calculatedCall(collection);398 });399}400401402