From ab9d17c7b6083bda9736dffada79552abdffcaa7 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Fri, 22 Sep 2023 13:54:43 +0100 Subject: [PATCH] build: write Hardhat task for gas benchmark --- hardhat.config.ts | 3 + scripts/ci/gas_benchmark.ts | 48 ++++++++++++++ scripts/ci/gas_benchmark_template.json | 90 ++++++++++++++++++++++++++ tests/Benchmark.test.ts | 69 +++++++++++++++++++- 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 scripts/ci/gas_benchmark.ts create mode 100644 scripts/ci/gas_benchmark_template.json diff --git a/hardhat.config.ts b/hardhat.config.ts index 78aeec4d1..175e5dad2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,7 +18,10 @@ import '@nomicfoundation/hardhat-toolbox'; import 'hardhat-packager'; import 'hardhat-contract-sizer'; import 'hardhat-deploy'; + +// custom built hardhat plugins for CI import './scripts/ci/docs-generate'; +import './scripts/ci/gas_benchmark'; // Typescript types for web3.js import '@nomiclabs/hardhat-web3'; diff --git a/scripts/ci/gas_benchmark.ts b/scripts/ci/gas_benchmark.ts new file mode 100644 index 000000000..5c96b9f6e --- /dev/null +++ b/scripts/ci/gas_benchmark.ts @@ -0,0 +1,48 @@ +import fs from 'fs'; +import { task } from 'hardhat/config'; +import { Align, getMarkdownTable, Row } from 'markdown-table-ts'; + +task('gas-benchmark', 'Benchmark gas usage of the smart contracts based on predefined scenarios') + .addParam( + 'compare', + 'The `.json` file that contains the gas costs of the currently compiled contracts (e.g: current working branch)', + ) + .addParam( + 'against', + 'The `.json` file that contains the gas costs to compare against (e.g: the `develop` branch)', + ) + .setAction(async function (args, hre, runSuper) { + // TODO: WIP + const currentBenchmark = JSON.parse(fs.readFileSync(args.compare, 'utf8')); + const baseBenchmark = JSON.parse(fs.readFileSync(args.against, 'utf8')); + + const casesExecute: Row[] = []; + + for (const [key, value] of Object.entries(currentBenchmark['runtime_costs']['execute'])) { + const gasDiffMainController = + value['main_controller'] - + baseBenchmark['runtime_costs']['execute'][key]['main_controller']; + + const gasDiffRestrictedController = + value['restricted_controller'] - + baseBenchmark['runtime_costs']['execute'][key]['restricted_controller']; + + casesExecute.push([ + value['description'], + value['main_controller'] + ` (${gasDiffMainController})`, + value['restricted_controller'] + ` (${gasDiffRestrictedController})`, + ]); + } + + const generatedTable = getMarkdownTable({ + table: { + head: ['`execute` scenarios', '👑 main controller', '🛃 restricted controller'], + body: casesExecute, + }, + alignment: [Align.Left], + }); + + const file = 'new_gas_benchmark.md'; + + fs.writeFileSync(file, generatedTable); + }); diff --git a/scripts/ci/gas_benchmark_template.json b/scripts/ci/gas_benchmark_template.json new file mode 100644 index 000000000..ccb6d4d68 --- /dev/null +++ b/scripts/ci/gas_benchmark_template.json @@ -0,0 +1,90 @@ +{ + "deployment_costs": { + "Universal Profile": "", + "Key Manager": "", + "LSP1 Delegate UP": "", + "LSP7 Digital Asset": "", + "LSP8 Identifiable Digital Asset": "" + }, + "runtime_costs": { + "execute": { + "case_1": { + "description": "LYX transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_2": { + "description": "LYX transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + }, + "case_3": { + "description": "LSP7 token transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_4": { + "description": "LSP7 token transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + }, + "case_5": { + "description": "LSP8 NFT transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_6": { + "description": "LSP8 NFT transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + } + }, + "setData": { + "case_1": { + "description": "Update Profile details (LSP3Profile Metadata)", + "main_controller": "", + "restricted_controller": "" + }, + "case_2": { + "description": "Add a new controller with permission to `SET_DATA` + 3x allowed data keys:
`AddressPermissions[]`
+ `AddressPermissions[index]`
+ `AddressPermissions:Permissions:`
+ `AddressPermissions:AllowedERC725YDataKeys: 1. decrease `AddressPermissions[]` Array length
2. remove the controller address at `AddressPermissions[index]`
3. set \"0x\" for the controller permissions under AddressPermissions:Permissions:", + "main_controller": "", + "restricted_controller": "" + }, + "case_5": { + "description": "Write 5x new LSP12 Issued Assets", + "main_controller": "", + "restricted_controller": "" + }, + "case_6": { + "description": "Update 3x data keys (first 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_7": { + "description": "Update 3x data keys (middle 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_8": { + "description": "Update 3x data keys (last 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_9": { + "description": "Set 2 x new data keys + add 3x new controllers", + "main_controller": "", + "restricted_controller": "" + } + } + } +} diff --git a/tests/Benchmark.test.ts b/tests/Benchmark.test.ts index 1d4cdc8a9..aa3ac5c5a 100644 --- a/tests/Benchmark.test.ts +++ b/tests/Benchmark.test.ts @@ -652,6 +652,18 @@ describe('⛽📊 Gas Benchmark', () => { }); describe('KeyManager', () => { + let gasBenchmark; + + before('setup benchmark file', async () => { + gasBenchmark = JSON.parse( + fs.readFileSync('./scripts/ci/gas_benchmark_template.json', 'utf8'), + ); + }); + + after(async () => { + fs.writeFileSync('gas_benchmark_result.json', JSON.stringify(gasBenchmark, null, 2)); + }); + describe('`execute(...)` via Key Manager', () => { describe('main controller (this browser extension)', () => { const casesExecuteMainController: Row[] = []; @@ -718,6 +730,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer LYX to an EOA', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_1']['main_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers some LYXes to a UP', async () => { @@ -731,6 +746,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer LYX to a UP', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_2']['main_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an EOA (no data)', async () => { @@ -755,6 +773,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer tokens (LSP7) to an EOA (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_3']['main_controller'] = + receipt.gasUsed.toNumber(); }); it('transfer some tokens (LSP7) to a UP (no data)', async () => { @@ -779,6 +800,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer tokens (LSP7) to a UP (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_4']['main_controller'] = + receipt.gasUsed.toNumber(); }); it('transfer a NFT (LSP8) to a EOA (no data)', async () => { @@ -803,6 +827,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer a NFT (LSP8) to a EOA (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_5']['main_controller'] = + receipt.gasUsed.toNumber(); }); it('transfer a NFT (LSP8) to a UP (no data)', async () => { @@ -827,6 +854,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfer a NFT (LSP8) to a UP (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_6']['main_controller'] = + receipt.gasUsed.toNumber(); }); after(async () => { @@ -944,7 +974,7 @@ describe('⛽📊 Gas Benchmark', () => { PERMISSIONS.TRANSFERVALUE, PERMISSIONS.CALL, PERMISSIONS.CALL, - combineAllowedCalls([CALLTYPE.VALUE], [allowedAddressToTransferValue], ["0xffffffff"], ["0xffffffff"]), + combineAllowedCalls([CALLTYPE.VALUE, CALLTYPE.VALUE], [allowedAddressToTransferValue, aliceUP.address], ["0xffffffff", "0xffffffff"], ["0xffffffff", "0xffffffff"]), combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], [lsp7MetaCoin.address, lsp7LyxDai.address], @@ -961,7 +991,7 @@ describe('⛽📊 Gas Benchmark', () => { ) }); - it('transfer some LYXes to an EOA - restricted to 1 x allowed address only (TRANSFERVALUE + 1x AllowedCalls)', async () => { + it('transfer some LYXes to an EOA - restricted to 2 x allowed address only (TRANSFERVALUE + 2x AllowedCalls)', async () => { const lyxAmount = 10; const tx = await context.universalProfile @@ -970,9 +1000,30 @@ describe('⛽📊 Gas Benchmark', () => { const receipt = await tx.wait(); casesExecuteRestrictedController.push([ - 'transfer some LYXes to an EOA - restricted to 1 x allowed address only (TRANSFERVALUE + 1x AllowedCalls)', + 'transfer some LYXes to an EOA - restricted to 2 x allowed address only (an EOA + a UP) (TRANSFERVALUE + 2x AllowedCalls)', + receipt.gasUsed.toNumber().toString(), + ]); + + gasBenchmark['runtime_costs']['execute']['case_1']['restricted_controller'] = + receipt.gasUsed.toNumber(); + }); + + it('transfer some LYXes to a UP - restricted to 2 x allowed address only (an EOA + a UP) (TRANSFERVALUE + 2x AllowedCalls)', async () => { + // ... + const lyxAmount = 10; + + const tx = await context.universalProfile + .connect(canTransferValueToOneAddress) + .execute(OPERATION_TYPES.CALL, aliceUP.address, lyxAmount, '0x'); + const receipt = await tx.wait(); + + casesExecuteRestrictedController.push([ + 'transfer some LYXes to a UP - restricted to 2 x allowed address only (an EOA + a UP) (TRANSFERVALUE + 2x AllowedCalls)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_2']['restricted_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an EOA - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -997,6 +1048,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfers some tokens (LSP7) to an EOA - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_3']['restricted_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an other UP - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1021,6 +1075,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfers some tokens (LSP7) to an other UP - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_4']['restricted_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers a NFT (LSP8) to an EOA - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1045,6 +1102,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfers a NFT (LSP8) to an EOA - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_5']['restricted_controller'] = + receipt.gasUsed.toNumber(); }); it('transfers a NFT (LSP8) to an other UP - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1069,6 +1129,9 @@ describe('⛽📊 Gas Benchmark', () => { 'transfers a NFT (LSP8) to an other UP - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', receipt.gasUsed.toNumber().toString(), ]); + + gasBenchmark['runtime_costs']['execute']['case_6']['restricted_controller'] = + receipt.gasUsed.toNumber(); }); after(async () => {