difftreelog
feat(bench) added fee benchmarks for erc721 functions
in: master
3 files changed
tests/src/benchmarks/mintFee/benchmark.tsdiffbeforeafterboth1import {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}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}tests/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 {
tests/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;