1import {usingEthPlaygrounds} from '@unique/test-utils/eth/util.js';2import {EthUniqueHelper} from '@unique/test-utils/eth/index.js';3import {readFile} from 'fs/promises';4import type {ICrossAccountId} from '@unique-nft/playgrounds/types.js';5import type {IKeyringPair} from '@polkadot/types/types';6import {UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js';7import {Contract} from 'web3-eth-contract';8import {createObjectCsvWriter} from 'csv-writer';9import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common.js';10import {makeNames} from '@unique/test-utils/util.js';11import type {ContractImports} from '@unique/test-utils/eth/types.js';1213const {dirname} = makeNames(import.meta.url);1415const EVM_ABI_DIR = `${dirname}/../../../evm-abi`;1617export const CONTRACT_IMPORT: ContractImports[] = [18 {19 fsPath: `${EVM_ABI_DIR}/api/CollectionHelpers.sol`,20 solPath: 'eth/api/CollectionHelpers.sol',21 },22 {23 fsPath: `${EVM_ABI_DIR}/api/ContractHelpers.sol`,24 solPath: 'eth/api/ContractHelpers.sol',25 },26 {27 fsPath: `${EVM_ABI_DIR}/api/UniqueRefungibleToken.sol`,28 solPath: 'eth/api/UniqueRefungibleToken.sol',29 },30 {31 fsPath: `${EVM_ABI_DIR}/api/UniqueRefungible.sol`,32 solPath: 'eth/api/UniqueRefungible.sol',33 },34 {35 fsPath: `${EVM_ABI_DIR}/api/UniqueNFT.sol`,36 solPath: 'eth/api/UniqueNFT.sol',37 },38];3940interface IBenchmarkResultForProp {41 propertiesNumber: number;42 substrateFee: number;43 ethFee: number;44 ethBulkFee: number;45 ethMintCrossFee: number;46 evmProxyContractFee: number;47 evmProxyContractBulkFee: number;48}4950const main = async () => {51 const benchmarks = [52 'substrateFee',53 'ethFee',54 'ethBulkFee',55 'ethMintCrossFee',56 'evmProxyContractFee',57 'evmProxyContractBulkFee',58 ];59 const headers = [60 'propertiesNumber',61 ...benchmarks,62 ];636465 const csvWriter = createObjectCsvWriter({66 path: 'properties.csv',67 header: headers,68 });6970 await usingEthPlaygrounds(async (helper, privateKey) => {71 const CONTRACT_SOURCE = (72 await readFile(`${dirname}/proxyContract.sol`)73 ).toString();7475 const donor = await privateKey('//Alice'); 76 const ethSigner = await helper.eth.createAccountWithBalance(donor);7778 const contract = await helper.ethContract.deployByCode(79 ethSigner,80 'ProxyMint',81 CONTRACT_SOURCE,82 CONTRACT_IMPORT,83 );8485 const fees = await benchMintFee(helper, privateKey, contract);86 console.log('Minting without properties');87 console.table(fees);8889 const result: IBenchmarkResultForProp[] = [];90 const csvResult: IBenchmarkResultForProp[] = [];9192 for(let i = 1; i <= 20; i++) {93 const benchResult = await benchMintWithProperties(helper, privateKey, contract, {94 propertiesNumber: i,95 }) as any;9697 csvResult.push(benchResult);9899 const minFee = Math.min(...(benchmarks.map(x => benchResult[x])));100 for(const key of benchmarks) {101 const keyPercent = Math.round((benchResult[key] / minFee) * 100);102 benchResult[key] = `${benchResult[key]} (${keyPercent}%)`;103 }104105 result.push(benchResult);106 }107108 await csvWriter.writeRecords(csvResult);109110 console.log('Minting with properties');111 console.table(result, headers);112 });113};114115main()116 .then(() => process.exit(0))117 .catch((e) => {118 console.log(e);119 process.exit(1);120 });121122123124async function benchMintFee(125 helper: EthUniqueHelper,126 privateKey: (seed: string) => Promise<IKeyringPair>,127 proxyContract: Contract,128): Promise<{129 substrateFee: number;130 ethFee: number;131 evmProxyContractFee: number;132}> {133 const donor = await privateKey('//Alice');134 const substrateReceiver = await privateKey('//Bob');135 const ethSigner = await helper.eth.createAccountWithBalance(donor);136137 const nominal = helper.balance.getOneTokenNominal();138139 await helper.eth.transferBalanceFromSubstrate(140 donor,141 proxyContract.options.address,142 100n,143 );144145 const collection = (await createCollectionForBenchmarks(146 'nft',147 helper,148 privateKey,149 ethSigner,150 proxyContract.options.address,151 PERMISSIONS,152 )) as UniqueNFTCollection;153154 const substrateFee = await helper.arrange.calculcateFee(155 {Substrate: donor.address},156 () => collection.mintToken(donor, {Substrate: substrateReceiver.address}),157 );158159 const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId);160 const collectionContract = await helper.ethNativeContract.collection(161 collectionEthAddress,162 'nft',163 );164165 const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address);166167 const encodedCall = collectionContract.methods168 .mint(receiverEthAddress)169 .encodeABI();170171 const ethFee = await helper.arrange.calculcateFee(172 {Substrate: donor.address},173 async () => {174 await helper.eth.sendEVM(175 donor,176 collectionContract.options.address,177 encodedCall,178 '0',179 );180 },181 );182183 const evmProxyContractFee = await helper.arrange.calculcateFee(184 {Ethereum: ethSigner},185 async () => {186 await proxyContract.methods187 .mintToSubstrate(188 helper.ethAddress.fromCollectionId(collection.collectionId),189 substrateReceiver.addressRaw,190 )191 .send({from: ethSigner});192 },193 );194195 return {196 substrateFee: convertToTokens(substrateFee, nominal),197 ethFee: convertToTokens(ethFee, nominal),198 evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal),199 };200}201202async function benchMintWithProperties(203 helper: EthUniqueHelper,204 privateKey: (seed: string) => Promise<IKeyringPair>,205 proxyContract: Contract,206 setup: { propertiesNumber: number },207): Promise<IBenchmarkResultForProp> {208 const donor = await privateKey('//Alice'); 209 const ethSigner = await helper.eth.createAccountWithBalance(donor);210211 const susbstrateReceiver = await privateKey('//Bob');212 const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address);213214 const nominal = helper.balance.getOneTokenNominal();215216 const substrateFee = await calculateFeeNftMintWithProperties(217 helper,218 privateKey,219 {Substrate: donor.address},220 ethSigner,221 proxyContract.options.address,222 async (collection) => {223 await collection.mintToken(224 donor,225 {Substrate: susbstrateReceiver.address},226 PROPERTIES.slice(0, setup.propertiesNumber).map((p) => ({key: p.key, value: Buffer.from(p.value).toString()})),227 );228 },229 );230231 const ethFee = await calculateFeeNftMintWithProperties(232 helper,233 privateKey,234 {Substrate: donor.address},235 ethSigner,236 proxyContract.options.address,237 async (collection) => {238 const evmContract = await helper.ethNativeContract.collection(239 helper.ethAddress.fromCollectionId(collection.collectionId),240 'nft',241 undefined,242 true,243 );244245 const subTokenId = await evmContract.methods.nextTokenId().call();246247 let encodedCall = evmContract.methods248 .mint(receiverEthAddress)249 .encodeABI();250251 await helper.eth.sendEVM(252 donor,253 evmContract.options.address,254 encodedCall,255 '0',256 );257258 for(const val of PROPERTIES.slice(0, setup.propertiesNumber)) {259 encodedCall = await evmContract.methods260 .setProperty(subTokenId, val.key, Buffer.from(val.value))261 .encodeABI();262263 await helper.eth.sendEVM(264 donor,265 evmContract.options.address,266 encodedCall,267 '0',268 );269 }270 },271 );272273 const ethBulkFee = await calculateFeeNftMintWithProperties(274 helper,275 privateKey,276 {Substrate: donor.address},277 ethSigner,278 proxyContract.options.address,279 async (collection) => {280 const evmContract = await helper.ethNativeContract.collection(281 helper.ethAddress.fromCollectionId(collection.collectionId),282 'nft',283 );284285 const subTokenId = await evmContract.methods.nextTokenId().call();286287 let encodedCall = evmContract.methods288 .mint(receiverEthAddress)289 .encodeABI();290291 await helper.eth.sendEVM(292 donor,293 evmContract.options.address,294 encodedCall,295 '0',296 );297298 encodedCall = await evmContract.methods299 .setProperties(300 subTokenId,301 PROPERTIES.slice(0, setup.propertiesNumber),302 )303 .encodeABI();304305 await helper.eth.sendEVM(306 donor,307 evmContract.options.address,308 encodedCall,309 '0',310 );311 },312 );313314 const ethMintCrossFee = await calculateFeeNftMintWithProperties(315 helper,316 privateKey,317 {Ethereum: ethSigner},318 ethSigner,319 proxyContract.options.address,320 async (collection) => {321 const evmContract = await helper.ethNativeContract.collection(322 helper.ethAddress.fromCollectionId(collection.collectionId),323 'nft',324 );325326 await evmContract.methods.mintCross(327 helper.ethCrossAccount.fromAddress(receiverEthAddress),328 PROPERTIES.slice(0, setup.propertiesNumber),329 )330 .send({from: ethSigner});331 },332 );333334 const proxyContractFee = await calculateFeeNftMintWithProperties(335 helper,336 privateKey,337 {Ethereum: ethSigner},338 ethSigner,339 proxyContract.options.address,340 async (collection) => {341 await proxyContract.methods342 .mintToSubstrateWithProperty(343 helper.ethAddress.fromCollectionId(collection.collectionId),344 susbstrateReceiver.addressRaw,345 PROPERTIES.slice(0, setup.propertiesNumber),346 )347 .send({from: ethSigner});348 },349 );350351 const proxyContractBulkFee = await calculateFeeNftMintWithProperties(352 helper,353 privateKey,354 {Ethereum: ethSigner},355 ethSigner,356 proxyContract.options.address,357 async (collection) => {358 await proxyContract.methods359 .mintToSubstrateBulkProperty(360 helper.ethAddress.fromCollectionId(collection.collectionId),361 susbstrateReceiver.addressRaw,362 PROPERTIES.slice(0, setup.propertiesNumber),363 )364 .send({from: ethSigner});365 },366 );367368 return {369 propertiesNumber: setup.propertiesNumber,370 substrateFee: convertToTokens(substrateFee, nominal),371 ethFee: convertToTokens(ethFee, nominal),372 ethBulkFee: convertToTokens(ethBulkFee, nominal),373 ethMintCrossFee: convertToTokens(ethMintCrossFee, nominal),374 evmProxyContractFee: convertToTokens(proxyContractFee, nominal),375 evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),376 };377}378379async function calculateFeeNftMintWithProperties(380 helper: EthUniqueHelper,381 privateKey: (seed: string) => Promise<IKeyringPair>,382 payer: ICrossAccountId,383 ethSigner: string,384 proxyContractAddress: string,385 calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,386): Promise<bigint> {387 const collection = (await createCollectionForBenchmarks(388 'nft',389 helper,390 privateKey,391 ethSigner,392 proxyContractAddress,393 PERMISSIONS,394 )) as UniqueNFTCollection;395 return helper.arrange.calculcateFee(payer, async () => {396 await calculatedCall(collection);397 });398}399400401