git.delta.rocks / unique-network / refs/commits / 40343ed090f3

difftreelog

source

tests/src/benchmarks/mintFee/benchmark.ts16.9 KiBsourcehistory
1import {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}