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 () => {