1import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util';2import {readFile} from 'fs/promises';3import {ContractImports} 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 evmProxyContractFee: number;62 evmProxyContractBulkFee: number;63}6465const main = async () => {66 const csvWriter = createObjectCsvWriter({67 path: 'properties.csv',68 header: [69 'propertiesNumber',70 'substrateFee',71 'ethFee',72 'ethBulkFee',73 'evmProxyContractFee',74 'evmProxyContractBulkFee',75 ],76 });7778 await usingEthPlaygrounds(async (helper, privateKey) => {79 const CONTRACT_SOURCE = (80 await readFile(`${__dirname}/proxyContract.sol`)81 ).toString();8283 const donor = await privateKey('//Alice'); 84 const myAccount = await privateKey('//Bob'); 85 const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);8687 const contract = await helper.ethContract.deployByCode(88 ethSigner,89 'ProxyMint',90 CONTRACT_SOURCE,91 CONTRACT_IMPORT,92 );9394 const fees = await benchMintFee(helper, privateKey, contract);95 console.log(fees);9697 const result: IBenchmarkResultForProp[] = [];9899 for (let i = 1; i <= 20; i++) {100 result.push(await benchMintWithProperties(helper, privateKey, contract, {101 propertiesNumber: i,102 }));103 }104105 await csvWriter.writeRecords(result);106107 console.table(result);108 });109};110111main()112 .then(() => process.exit(0))113 .catch((e) => {114 console.log(e);115 process.exit(1);116 });117118async function createCollectionForBenchmarks(119 helper: EthUniqueHelper,120 privateKey: (seed: string) => Promise<IKeyringPair>,121 ethSigner: string,122 proxyContract: string,123 permissions: ITokenPropertyPermission[],124) {125 const donor = await privateKey('//Alice');126127 const collection = await helper.nft.mintCollection(donor, {128 name: 'test mintToSubstrate',129 description: 'EVMHelpers',130 tokenPrefix: 'ap',131 tokenPropertyPermissions: [132 {133 key: 'url',134 permission: {135 tokenOwner: true,136 collectionAdmin: true,137 mutable: true,138 },139 },140 ],141 limits: {sponsorTransferTimeout: 0, sponsorApproveTimeout: 0},142 permissions: {mintMode: true},143 });144145 await collection.addToAllowList(donor, {146 Ethereum: helper.address.substrateToEth(donor.address),147 });148 await collection.addToAllowList(donor, {Substrate: donor.address});149 await collection.addAdmin(donor, {Ethereum: ethSigner});150 await collection.addAdmin(donor, {151 Ethereum: helper.address.substrateToEth(donor.address),152 });153 await collection.addToAllowList(donor, {Ethereum: proxyContract});154 await collection.addAdmin(donor, {Ethereum: proxyContract});155 await collection.setTokenPropertyPermissions(donor, permissions);156157 return collection;158}159160async function benchMintFee(161 helper: EthUniqueHelper,162 privateKey: (seed: string) => Promise<IKeyringPair>,163 proxyContract: Contract,164): Promise<{165 substrateFee: number;166 ethFee: number;167 evmProxyContractFee: number;168}> {169 const donor = await privateKey('//Alice');170 const substrateReceiver = await privateKey('//Bob');171 const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);172173 const nominal = helper.balance.getOneTokenNominal();174175 await helper.eth.transferBalanceFromSubstrate(176 donor,177 proxyContract.options.address,178 100n,179 );180181 const collection = await createCollectionForBenchmarks(182 helper,183 privateKey,184 ethSigner,185 proxyContract.options.address,186 PERMISSIONS,187 );188189 const substrateFee = await helper.arrange.calculcateFee(190 {Substrate: donor.address},191 () => collection.mintToken(donor, {Substrate: substrateReceiver.address}),192 );193194 const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId);195 const collectionContract = helper.ethNativeContract.collection(196 collectionEthAddress,197 'nft',198 );199200 const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address);201202 const encodedCall = collectionContract.methods203 .mint(receiverEthAddress)204 .encodeABI();205206 const ethFee = await helper.arrange.calculcateFee(207 {Substrate: donor.address},208 async () => {209 await helper.eth.sendEVM(210 donor,211 collectionContract.options.address,212 encodedCall,213 '0',214 );215 },216 );217218 const evmProxyContractFee = await helper.arrange.calculcateFee(219 {Ethereum: ethSigner},220 async () => {221 await proxyContract.methods222 .mintToSubstrate(223 helper.ethAddress.fromCollectionId(collection.collectionId),224 substrateReceiver.addressRaw,225 )226 .send({from: ethSigner});227 },228 );229230 return {231 substrateFee: convertToTokens(substrateFee, nominal),232 ethFee: convertToTokens(ethFee, nominal),233 evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal),234 };235}236237async function benchMintWithProperties(238 helper: EthUniqueHelper,239 privateKey: (seed: string) => Promise<IKeyringPair>,240 proxyContract: Contract,241 setup: { propertiesNumber: number },242): Promise<IBenchmarkResultForProp> {243 const donor = await privateKey('//Alice'); 244 const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n);245246 const susbstrateReceiver = await privateKey('//Bob');247 const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address);248249 const nominal = helper.balance.getOneTokenNominal();250251 const substrateFee = await calculateFeeNftMintWithProperties(252 helper,253 privateKey,254 {Substrate: donor.address},255 ethSigner,256 proxyContract.options.address,257 async (collection) => {258 await collection.mintToken(259 donor,260 {Substrate: susbstrateReceiver.address},261 PROPERTIES.slice(0, setup.propertiesNumber).map((p) => {262 return {key: p.key, value: Buffer.from(p.value).toString()};263 }),264 );265 },266 );267268 const ethFee = await calculateFeeNftMintWithProperties(269 helper,270 privateKey,271 {Substrate: donor.address},272 ethSigner,273 proxyContract.options.address,274 async (collection) => {275 const evmContract = helper.ethNativeContract.collection(276 helper.ethAddress.fromCollectionId(collection.collectionId),277 'nft',278 );279280 const subTokenId = await evmContract.methods.nextTokenId().call();281282 let encodedCall = evmContract.methods283 .mint(receiverEthAddress)284 .encodeABI();285286 await helper.eth.sendEVM(287 donor,288 evmContract.options.address,289 encodedCall,290 '0',291 );292293 for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) {294 encodedCall = await evmContract.methods295 .setProperty(subTokenId, val.key, Buffer.from(val.value))296 .encodeABI();297298 await helper.eth.sendEVM(299 donor,300 evmContract.options.address,301 encodedCall,302 '0',303 );304 }305 },306 );307308 const ethBulkFee = await calculateFeeNftMintWithProperties(309 helper,310 privateKey,311 {Substrate: donor.address},312 ethSigner,313 proxyContract.options.address,314 async (collection) => {315 const evmContract = helper.ethNativeContract.collection(316 helper.ethAddress.fromCollectionId(collection.collectionId),317 'nft',318 );319320 const subTokenId = await evmContract.methods.nextTokenId().call();321322 let encodedCall = evmContract.methods323 .mint(receiverEthAddress)324 .encodeABI();325326 await helper.eth.sendEVM(327 donor,328 evmContract.options.address,329 encodedCall,330 '0',331 );332333 encodedCall = await evmContract.methods334 .setProperties(335 subTokenId,336 PROPERTIES.slice(0, setup.propertiesNumber).map((p) => {337 return {field_0: p.key, field_1: p.value};338 }),339 )340 .encodeABI();341342 await helper.eth.sendEVM(343 donor,344 evmContract.options.address,345 encodedCall,346 '0',347 );348 },349 );350351 const proxyContractFee = await calculateFeeNftMintWithProperties(352 helper,353 privateKey,354 {Ethereum: ethSigner},355 ethSigner,356 proxyContract.options.address,357 async (collection) => {358 await proxyContract.methods359 .mintToSubstrateWithProperty(360 helper.ethAddress.fromCollectionId(collection.collectionId),361 susbstrateReceiver.addressRaw,362 PROPERTIES.slice(0, setup.propertiesNumber),363 )364 .send({from: ethSigner});365 },366 );367368 const proxyContractBulkFee = await calculateFeeNftMintWithProperties(369 helper,370 privateKey,371 {Ethereum: ethSigner},372 ethSigner,373 proxyContract.options.address,374 async (collection) => {375 await proxyContract.methods376 .mintToSubstrateBulkProperty(377 helper.ethAddress.fromCollectionId(collection.collectionId),378 susbstrateReceiver.addressRaw,379 PROPERTIES.slice(0, setup.propertiesNumber).map((p) => {380 return {field_0: p.key, field_1: p.value};381 }),382 )383 .send({from: ethSigner, gas: 25_000_000});384 },385 );386387 return {388 propertiesNumber: setup.propertiesNumber,389 substrateFee: convertToTokens(substrateFee, nominal),390 ethFee: convertToTokens(ethFee, nominal),391 ethBulkFee: convertToTokens(ethBulkFee, nominal),392 evmProxyContractFee: convertToTokens(proxyContractFee, nominal),393 evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal),394 };395}396397async function calculateFeeNftMintWithProperties(398 helper: EthUniqueHelper,399 privateKey: (seed: string) => Promise<IKeyringPair>,400 payer: ICrossAccountId,401 ethSigner: string,402 proxyContractAddress: string,403 calculatedCall: (collection: UniqueNFTCollection) => Promise<any>,404): Promise<bigint> {405 const collection = await createCollectionForBenchmarks(406 helper,407 privateKey,408 ethSigner,409 proxyContractAddress,410 PERMISSIONS,411 );412 return helper.arrange.calculcateFee(payer, async () => {413 await calculatedCall(collection);414 });415}416function convertToTokens(value: bigint, nominal: bigint): number {417 return Number((value * 1000n) / nominal) / 1000;418}