Skip to content

Commit

Permalink
build: write Hardhat task for gas benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
CJ42 committed Sep 26, 2023
1 parent 3308f4c commit 445be6c
Show file tree
Hide file tree
Showing 4 changed files with 1,006 additions and 340 deletions.
3 changes: 3 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
207 changes: 207 additions & 0 deletions scripts/ci/gas_benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
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) {
const currentBenchmark = JSON.parse(fs.readFileSync(args.compare, 'utf8'));
const baseBenchmark = JSON.parse(fs.readFileSync(args.against, 'utf8'));

const casesEOAExecute: Row[] = [];
const casesEOASetData: Row[] = [];
const casesEOAToken: Row[] = [];

const casesExecute: Row[] = [];
const casesSetData: Row[] = [];

const numberWithCommas = (value) => {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};

const displayGasDiffText = (gasDiff) => {
const gasDiffNumber = parseInt(gasDiff);

let emoji;

if (gasDiffNumber === 0) {
emoji = '';
} else {
emoji = parseInt(gasDiff) < 0 ? '✅' : '❌';
}

return ` ( ${numberWithCommas(gasDiff)} ${emoji})`;
};

// EOA - Execute
for (const [key, value] of Object.entries(
currentBenchmark['runtime_costs']['EOA_owner']['execute'],
)) {
const gasDiff =
value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['execute'][key]['gas_cost'];

casesEOAExecute.push([
value['description'],
numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff),
]);
}

const generatedExecuteEOATable = getMarkdownTable({
table: {
head: ['`execute` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'],
body: casesEOAExecute,
},
alignment: [Align.Left],
});

// EOA - Set Data
for (const [key, value] of Object.entries(
currentBenchmark['runtime_costs']['EOA_owner']['setData'],
)) {
const gasDiff =
value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['setData'][key]['gas_cost'];

casesEOASetData.push([
value['description'],
numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff),
]);
}

const generatedSetDataEOATable = getMarkdownTable({
table: {
head: ['`setData` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'],
body: casesEOASetData,
},
alignment: [Align.Left],
});

// EOA - Token
for (const [key, value] of Object.entries(
currentBenchmark['runtime_costs']['EOA_owner']['tokens'],
)) {
const gasDiff =
value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['tokens'][key]['gas_cost'];

casesEOAToken.push([
value['description'],
numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff),
]);
}

const generatedTokenEOATable = getMarkdownTable({
table: {
head: ['`execute` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'],
body: casesEOAToken,
},
alignment: [Align.Left],
});

// Key Manager - Execute
for (const [key, value] of Object.entries(
currentBenchmark['runtime_costs']['KeyManager_owner']['execute'],
)) {
const gasDiffMainController =
value['main_controller'] -
baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['main_controller'];

const gasDiffRestrictedController =
value['restricted_controller'] -
baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['restricted_controller'];

casesExecute.push([
value['description'],
numberWithCommas(value['main_controller']) + displayGasDiffText(gasDiffMainController),
numberWithCommas(value['restricted_controller']) +
displayGasDiffText(gasDiffRestrictedController),
]);
}

const generatedExecuteTable = getMarkdownTable({
table: {
head: ['`execute` scenarios', '👑 main controller', '🛃 restricted controller'],
body: casesExecute,
},
alignment: [Align.Left],
});

// Key Manager - Set Data
for (const [key, value] of Object.entries(
currentBenchmark['runtime_costs']['KeyManager_owner']['setData'],
)) {
const gasDiffMainController =
value['main_controller'] -
baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['main_controller'];

const gasDiffRestrictedController =
value['restricted_controller'] -
baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['restricted_controller'];

casesSetData.push([
value['description'],
numberWithCommas(value['main_controller']) + displayGasDiffText(gasDiffMainController),
numberWithCommas(value['restricted_controller']) +
displayGasDiffText(gasDiffRestrictedController),
]);
}

const generatedSetDataTable = getMarkdownTable({
table: {
head: ['`setData` scenarios', '👑 main controller', '🛃 restricted controller'],
body: casesSetData,
},
alignment: [Align.Left],
});

const file = 'gas_benchmark.md';

const markdown = `
👋 Hello
⛽ I am the Gas Bot Reporter. I keep track of the gas costs of common interactions using Universal Profiles 🆙 !
📊 Here is a summary of the gas cost with the code introduced by this PR.
<details>
<summary>⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager</summary>
This document contains the gas usage for common interactions and scenarios when using UniversalProfile smart contracts.
> **Note:** the \`execute\` and \`setData\` scenarios are executed on a fresh UniversalProfile and LSP6KeyManager smart contracts, deployed as standard contracts (not as proxy behind a base contract implementation).
### 🔀 \`execute\` scenarios
${generatedExecuteTable}
### 🗄️ \`setData\` scenarios
${generatedSetDataTable}
</details>
<details>
<summary>⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager</summary>
### 🔀 \`execute\` scenarios
${generatedExecuteEOATable}
### 🗄️ \`setData\` scenarios
${generatedSetDataEOATable}
### 🗄️ \`Tokens\` scenarios
${generatedTokenEOATable}
</details>
`;

fs.writeFileSync(file, markdown);
});
Loading

0 comments on commit 445be6c

Please sign in to comment.