From d62239e7031ef80c52fb35abe5ecf996a9340141 Mon Sep 17 00:00:00 2001
From: Jean Cvllr <31145285+CJ42@users.noreply.github.com>
Date: Fri, 29 Sep 2023 17:05:31 +0100
Subject: [PATCH] ci: update gas benchmark CI to display gas diffs as well as
the figures (#731)
* build: write Hardhat task for gas benchmark
* refactor: improve benchmark generation script with markdown
* ci: update CI to generate gas comparison
* build: add deployment cost in gas benchmark
---
.github/workflows/benchmark.yml | 43 +-
.gitignore | 2 +-
hardhat.config.ts | 3 +
scripts/ci/gas_benchmark.ts | 226 ++++++
scripts/ci/gas_benchmark_template.json | 204 +++++
tests/Benchmark.test.ts | 1023 ++++++++++++++++--------
6 files changed, 1143 insertions(+), 358 deletions(-)
create mode 100644 scripts/ci/gas_benchmark.ts
create mode 100644 scripts/ci/gas_benchmark_template.json
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 0fb243e1f..edb5a2057 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -1,5 +1,7 @@
# This workflow benchmark the gas usage of Universal Profile for common interactions
-
+# It compare the gas cost of the changes made between:
+# - a feature branch (where a PR is opened)
+# - a target branch (where the PR will be merged)
name: ๐ ๐ Universal Profile Benchmark
on:
@@ -22,7 +24,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - name: Checkout base branch
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.base.sha }}
+ fetch-depth: 0
- name: Use Node.js '16.15.0'
uses: actions/setup-node@v2
@@ -37,11 +43,40 @@ jobs:
run: npx hardhat compile
- name: ๐งช Run Benchmark tests
- run: npm run test:benchmark
+ # Rename the file to be able to generate benchmark JSON report
+ run: |
+ npm run test:benchmark
+ mv gas_benchmark_result.json gas_benchmark_before.json
+
+ - name: Checkout current branch
+ uses: actions/checkout@v3
+ # Do not run `git clean -ffdx && git reset --hard HEAD` to prevent removing `gas_benchmark_before.json`
+ with:
+ clean: false
+
+ - name: Use Node.js '16.15.0'
+ uses: actions/setup-node@v2
+ with:
+ node-version: "16.15.0"
+ cache: "npm"
+
+ - name: ๐ฆ Install dependencies
+ run: npm ci
+
+ - name: ๐๏ธ Build contract artifacts
+ run: npx hardhat compile
+
+ - name: ๐งช Run Benchmark tests
+ run: |
+ npm run test:benchmark
+ mv gas_benchmark_result.json gas_benchmark_after.json
- name: ๐ Generate Benchmark Report
+ run: npx hardhat gas-benchmark --compare gas_benchmark_after.json --against gas_benchmark_before.json
+
+ - name: ๐ฌ Add Gas Report as comment in PR
uses: peter-evans/create-or-update-comment@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
- body-file: "./benchmark.md"
+ body-file: "./gas_benchmark.md"
diff --git a/.gitignore b/.gitignore
index 9b491c2bd..c1824b6ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,7 +132,7 @@ out/
forge-cache/
# generated gas benchmark
-benchmark.md
+gas_benchmark.md
# Exclude build output folders
/common
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..024a1b464
--- /dev/null
+++ b/scripts/ci/gas_benchmark.ts
@@ -0,0 +1,226 @@
+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) {
+ const currentBenchmark = JSON.parse(fs.readFileSync(args.compare, 'utf8'));
+ const baseBenchmark = JSON.parse(fs.readFileSync(args.against, 'utf8'));
+
+ const deploymentCosts: Row[] = [];
+
+ const casesEOAExecute: Row[] = [];
+ const casesEOASetData: Row[] = [];
+ const casesEOATokens: Row[] = [];
+
+ const casesKeyManagerExecute: Row[] = [];
+ const casesKeyManagerSetData: Row[] = [];
+
+ const formatNumber = (value: number) => {
+ return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+ };
+
+ const displayGasDiff = (gasDiff: number) => {
+ let emoji = '';
+
+ if (gasDiff > 0) {
+ emoji = '๐โ';
+ }
+
+ if (gasDiff < 0) {
+ emoji = '๐โ
';
+ }
+
+ return `${formatNumber(gasDiff)} ${emoji}`;
+ };
+
+ // Deployment costs
+ for (const [key, value] of Object.entries(currentBenchmark['deployment_costs'])) {
+ const gasCost: any = value;
+ const gasDiff = gasCost - baseBenchmark['deployment_costs'][key];
+
+ deploymentCosts.push([key, value + ` (${displayGasDiff(gasDiff)})`]);
+ }
+
+ const generatedDeploymentCostsTable = getMarkdownTable({
+ table: {
+ head: ['Deployed contracts', 'โฝ Deployment cost'],
+ body: deploymentCosts,
+ },
+ alignment: [Align.Left],
+ });
+
+ // 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'],
+ value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`,
+ ]);
+ }
+
+ const generatedEOAExecuteTable = getMarkdownTable({
+ table: {
+ head: ['`execute` scenarios - UP owned by ๐ EOA', 'โฝ Gas Usage'],
+ body: casesEOAExecute,
+ },
+ alignment: [Align.Left],
+ });
+
+ // EOA - setData
+ 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'],
+ value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`,
+ ]);
+ }
+
+ const generatedEOASetDataTable = getMarkdownTable({
+ table: {
+ head: ['`setData` scenarios - UP owned by ๐ EOA', 'โฝ Gas Usage'],
+ body: casesEOASetData,
+ },
+ alignment: [Align.Left],
+ });
+
+ // EOA - Tokens
+ 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'];
+
+ casesEOATokens.push([
+ value['description'],
+ value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`,
+ ]);
+ }
+
+ const generatedEOATokensTable = getMarkdownTable({
+ table: {
+ head: ['`Tokens` scenarios - UP owned by ๐ EOA', 'โฝ Gas Usage'],
+ body: casesEOATokens,
+ },
+ 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'];
+
+ casesKeyManagerExecute.push([
+ value['description'],
+ value['main_controller'] + ` (${displayGasDiff(gasDiffMainController)})`,
+ value['restricted_controller'] + ` (${displayGasDiff(gasDiffRestrictedController)})`,
+ ]);
+ }
+
+ const generatedKeyManagerExecuteTable = getMarkdownTable({
+ table: {
+ head: ['`execute` scenarios', '๐ main controller', '๐ restricted controller'],
+ body: casesKeyManagerExecute,
+ },
+ alignment: [Align.Left],
+ });
+
+ // Key Manager - setData
+ 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'];
+
+ casesKeyManagerSetData.push([
+ value['description'],
+ value['main_controller'] + ` (${displayGasDiff(gasDiffMainController)})`,
+ value['restricted_controller'] + ` (${displayGasDiff(gasDiffRestrictedController)})`,
+ ]);
+ }
+
+ const generatedKeyManagerSetDataTable = getMarkdownTable({
+ table: {
+ head: ['`setData` scenarios', '๐ main controller', '๐ restricted controller'],
+ body: casesKeyManagerSetData,
+ },
+ alignment: [Align.Left],
+ });
+
+ const markdownContent = `
+๐ 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.
+
+##ย โฝ๐ Gas Benchmark Report
+
+### Deployment Costs
+
+${generatedDeploymentCostsTable}
+
+### Runtime Costs
+
+
+UniversalProfile owned by an ๐ EOA
+
+### ๐ \`execute\` scenarios
+
+${generatedEOAExecuteTable}
+
+### ๐๏ธ \`setData\` scenarios
+
+${generatedEOASetDataTable}
+
+### ๐๏ธ \`Tokens\` scenarios
+
+${generatedEOATokensTable}
+
+
+
+
+UniversalProfile owned by a ๐๐ LSP6KeyManager
+
+### ๐ \`execute\` scenarios
+
+${generatedKeyManagerExecuteTable}
+
+### ๐๏ธ \`setData\` scenarios
+
+${generatedKeyManagerSetDataTable}
+
+
+
+ `;
+
+ const file = 'gas_benchmark.md';
+
+ fs.writeFileSync(file, markdownContent, 'utf8');
+ });
diff --git a/scripts/ci/gas_benchmark_template.json b/scripts/ci/gas_benchmark_template.json
new file mode 100644
index 000000000..6c7d794c6
--- /dev/null
+++ b/scripts/ci/gas_benchmark_template.json
@@ -0,0 +1,204 @@
+{
+ "deployment_costs": {
+ "UniversalProfile": "",
+ "KeyManager": "",
+ "LSP1DelegateUP": "",
+ "LSP7Mintable": "",
+ "LSP8Mintable": ""
+ },
+ "runtime_costs": {
+ "EOA_owner": {
+ "execute": {
+ "case_1": {
+ "description": "Transfer 1 LYX to an EOA without data",
+ "gas_cost": ""
+ },
+ "case_2": {
+ "description": "Transfer 1 LYX to a UP without data",
+ "gas_cost": ""
+ },
+ "case_3": {
+ "description": "Transfer 1 LYX to an EOA with 256 bytes of data",
+ "gas_cost": ""
+ },
+ "case_4": {
+ "description": "Transfer 1 LYX to a UP with 256 bytes of data",
+ "gas_cost": ""
+ },
+ "case_5": {
+ "description": "Transfer 0.1 LYX to 3x EOA without data",
+ "gas_cost": ""
+ },
+ "case_6": {
+ "description": "Transfer 0.1 LYX to 3x UP without data",
+ "gas_cost": ""
+ },
+ "case_7": {
+ "description": "Transfer 0.1 LYX to 3x EOA with 256 bytes of data",
+ "gas_cost": ""
+ },
+ "case_8": {
+ "description": "Transfer 0.1 LYX to 3x UPs with 256 bytes of data",
+ "gas_cost": ""
+ }
+ },
+ "setData": {
+ "case_1": {
+ "description": "Set a 20 bytes long value",
+ "gas_cost": ""
+ },
+ "case_2": {
+ "description": "Set a 60 bytes long value",
+ "gas_cost": ""
+ },
+ "case_3": {
+ "description": "Set a 160 bytes long value",
+ "gas_cost": ""
+ },
+ "case_4": {
+ "description": "Set a 300 bytes long value",
+ "gas_cost": ""
+ },
+ "case_5": {
+ "description": "Set a 600 bytes long value",
+ "gas_cost": ""
+ },
+ "case_6": {
+ "description": "Change the value of a data key already set",
+ "gas_cost": ""
+ },
+ "case_7": {
+ "description": "Remove the value of a data key already set",
+ "gas_cost": ""
+ },
+ "case_8": {
+ "description": "Set 2 data keys of 20 bytes long value",
+ "gas_cost": ""
+ },
+ "case_9": {
+ "description": "Set 2 data keys of 100 bytes long value",
+ "gas_cost": ""
+ },
+ "case_10": {
+ "description": "Set 3 data keys of 20 bytes long value",
+ "gas_cost": ""
+ },
+ "case_11": {
+ "description": "Change the value of three data keys already set of 20 bytes long value",
+ "gas_cost": ""
+ },
+ "case_12": {
+ "description": "Remove the value of three data keys already set",
+ "gas_cost": ""
+ }
+ },
+ "tokens": {
+ "case_1": {
+ "description": "Minting a LSP7Token to a UP (No Delegate) from an EOA",
+ "gas_cost": ""
+ },
+ "case_2": {
+ "description": "Minting a LSP7Token to an EOA from an EOA",
+ "gas_cost": ""
+ },
+ "case_3": {
+ "description": "Transferring an LSP7Token from a UP to another UP (No Delegate)",
+ "gas_cost": ""
+ },
+ "case_4": {
+ "description": "Minting a LSP8Token to a UP (No Delegate) from an EOA ",
+ "gas_cost": ""
+ },
+ "case_5": {
+ "description": "Minting a LSP8Token to an EOA from an EOA ",
+ "gas_cost": ""
+ },
+ "case_6": {
+ "description": "Transferring an LSP8Token from a UP to another UP (No Delegate)",
+ "gas_cost": ""
+ }
+ }
+ },
+ "KeyManager_owner": {
+ "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..aff0d2a1b 100644
--- a/tests/Benchmark.test.ts
+++ b/tests/Benchmark.test.ts
@@ -2,9 +2,10 @@ import fs from 'fs';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { Align, getMarkdownTable, Row } from 'markdown-table-ts';
import {
+ LSP1UniversalReceiverDelegateUP,
+ LSP1UniversalReceiverDelegateUP__factory,
LSP6KeyManager__factory,
LSP7Mintable,
LSP7Mintable__factory,
@@ -15,16 +16,21 @@ import {
} from '../types';
import {
- ALL_PERMISSIONS,
ERC725YDataKeys,
INTERFACE_IDS,
OPERATION_TYPES,
PERMISSIONS,
CALLTYPE,
+ LSP8_TOKEN_ID_TYPES,
} from '../constants';
import { LSP6TestContext } from './utils/context';
import { setupKeyManager, setupProfileWithKeyManagerWithURD } from './utils/fixtures';
-import { combineAllowedCalls, combinePermissions, encodeCompactBytesArray } from './utils/helpers';
+import {
+ abiCoder,
+ combineAllowedCalls,
+ combinePermissions,
+ encodeCompactBytesArray,
+} from './utils/helpers';
import { BigNumber } from 'ethers';
export type UniversalProfileContext = {
@@ -63,22 +69,82 @@ const buildUniversalProfileContext = async (
return { accounts, owner, universalProfile };
};
-let UniversalProfileSetDataTable;
-let UniversalProfileExecuteTable;
-let UniversalProfileTokensTable;
+describe('โฝ๐ Gas Benchmark', () => {
+ let gasBenchmark;
-let mainControllerExecuteTable;
-let restrictedControllerExecuteTable;
+ before('setup benchmark file', async () => {
+ gasBenchmark = JSON.parse(fs.readFileSync('./scripts/ci/gas_benchmark_template.json', 'utf8'));
+ });
-let mainControllerSetDataTable;
-let restrictedControllerSetDataTable;
+ after(async () => {
+ fs.writeFileSync('./gas_benchmark_result.json', JSON.stringify(gasBenchmark, null, 2));
+ });
+
+ describe('Deployment costs', () => {
+ it('deploy contracts + save deployment costs', async () => {
+ const accounts = await ethers.getSigners();
+
+ // Universal Profile
+ const universalProfile = await new UniversalProfile__factory(accounts[0]).deploy(
+ accounts[0].address,
+ );
+
+ const universalProfileDeployTransaction = universalProfile.deployTransaction;
+ const universalProfileDeploymentReceipt = await universalProfileDeployTransaction.wait();
+
+ gasBenchmark['deployment_costs']['UniversalProfile'] =
+ universalProfileDeploymentReceipt.gasUsed.toNumber();
+
+ // Key Manager
+ const keyManager = await new LSP6KeyManager__factory(accounts[0]).deploy(
+ universalProfile.address,
+ );
+
+ const keyManagerDeployTransaction = keyManager.deployTransaction;
+ const keyManagerDeploymentReceipt = await keyManagerDeployTransaction?.wait();
+
+ gasBenchmark['deployment_costs']['KeyManager'] =
+ keyManagerDeploymentReceipt?.gasUsed.toNumber();
+
+ // LSP1 Delegate
+ const lsp1Delegate = await new LSP1UniversalReceiverDelegateUP__factory(accounts[0]).deploy();
+
+ const lsp1DelegateDeployTransaction = lsp1Delegate.deployTransaction;
+ const lsp1DelegateDeploymentReceipt = await lsp1DelegateDeployTransaction.wait();
+
+ gasBenchmark['deployment_costs']['LSP1DelegateUP'] =
+ lsp1DelegateDeploymentReceipt.gasUsed.toNumber();
+
+ // LSP7 Token (Mintable preset)
+ const lsp7Mintable = await new LSP7Mintable__factory(accounts[0]).deploy(
+ 'Token',
+ 'MTKN',
+ accounts[0].address,
+ false,
+ );
+
+ const lsp7DeployTransaction = lsp7Mintable.deployTransaction;
+ const lsp7DeploymentReceipt = await lsp7DeployTransaction.wait();
+
+ gasBenchmark['deployment_costs']['LSP7Mintable'] = lsp7DeploymentReceipt.gasUsed.toNumber();
+
+ // LSP8 NFT (Mintable preset)
+ const lsp8Mintable = await new LSP8Mintable__factory(accounts[0]).deploy(
+ 'My NFT',
+ 'MNFT',
+ accounts[0].address,
+ LSP8_TOKEN_ID_TYPES.NUMBER,
+ );
+
+ const lsp8DeployTransaction = lsp8Mintable.deployTransaction;
+ const lsp8DeploymentReceipt = await lsp8DeployTransaction.wait();
+
+ gasBenchmark['deployment_costs']['LSP8Mintable'] = lsp8DeploymentReceipt.gasUsed.toNumber();
+ });
+ });
-describe('โฝ๐ Gas Benchmark', () => {
describe('UniversalProfile', () => {
let context: UniversalProfileContext;
- const executeUP: Row[] = [];
- const setDataUP: Row[] = [];
- const tokensUP: Row[] = [];
describe('execute', () => {
describe('execute Single', () => {
@@ -98,10 +164,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 1 LYX to an EOA without data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_1']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 1 LYX to a UP without data', async () => {
@@ -116,10 +180,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 1 LYX to a UP without data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_2']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 1 LYX to an EOA with 256 bytes of data', async () => {
@@ -134,10 +196,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 1 LYX to an EOA with 256 bytes of data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_3']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 1 LYX to a UP with 256 bytes of data', async () => {
@@ -152,15 +212,13 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 1 LYX to a UP with 256 bytes of data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_4']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
describe('execute Array', () => {
- let universalProfile1, universalProfile2, universalProfile3;
+ let universalProfile1: UniversalProfile, universalProfile2, universalProfile3;
before(async () => {
context = await buildUniversalProfileContext(ethers.utils.parseEther('50'));
@@ -198,10 +256,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 0.1 LYX to 3x EOA without data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_5']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 0.1 LYX to 3x UP without data', async () => {
@@ -220,10 +276,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 0.1 LYX to 3x UP without data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_6']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 0.1 LYX to 3x EOA with 256 bytes of data', async () => {
@@ -246,10 +300,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 0.1 LYX to 3x EOA with 256 bytes of data',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_7']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Transfer 0.1 LYX to 3x UP with 256 bytes of data', async () => {
@@ -273,20 +325,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- executeUP.push([
- 'Transfer 0.1 LYX to 3x EOA with 256 bytes of data',
- receipt.gasUsed.toNumber().toString(),
- ]);
- });
- });
-
- after(async () => {
- UniversalProfileExecuteTable = getMarkdownTable({
- table: {
- head: ['`execute` scenarios - ๐ UP Owner', 'โฝ Gas Usage'],
- body: executeUP,
- },
- alignment: [Align.Left, Align.Center],
+ gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_8']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
});
@@ -305,7 +345,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push(['Set a 20 bytes long value', receipt.gasUsed.toNumber().toString()]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_1']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set a 60 bytes long value', async () => {
@@ -316,7 +357,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push(['Set a 60 bytes long value', receipt.gasUsed.toNumber().toString()]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_2']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set a 160 bytes long value', async () => {
@@ -327,7 +369,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push(['Set a 160 bytes long value', receipt.gasUsed.toNumber().toString()]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_3']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set a 300 bytes long value', async () => {
@@ -338,7 +381,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push(['Set a 300 bytes long value', receipt.gasUsed.toNumber().toString()]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_4']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set a 600 bytes long value', async () => {
@@ -349,7 +393,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push(['Set a 600 bytes long value', receipt.gasUsed.toNumber().toString()]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_5']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Change the value of a data key already set', async () => {
@@ -363,10 +408,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Change the value of a data key already set',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_6']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Remove the value of a data key already set', async () => {
@@ -379,10 +422,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Remove the value of a data key already set',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_7']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
@@ -402,10 +443,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Set 2 data keys of 20 bytes long value',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_8']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set 2 data keys of 100 bytes long value', async () => {
@@ -419,10 +458,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Set 2 data keys of 100 bytes long value',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_9']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Set 3 data keys of 20 bytes long value', async () => {
@@ -442,10 +479,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Set 3 data keys of 20 bytes long value',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_10']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Change the value of three data keys already set of 20 bytes long value', async () => {
@@ -467,10 +502,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Change the value of three data keys already set of 20 bytes long value',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_11']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('Remove the value of three data keys already set', async () => {
@@ -492,20 +525,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- setDataUP.push([
- 'Remove the value of three data keys already set',
- receipt.gasUsed.toNumber().toString(),
- ]);
- });
- });
-
- after(async () => {
- UniversalProfileSetDataTable = getMarkdownTable({
- table: {
- head: ['`setData` scenarios - ๐ UP Owner', 'โฝ Gas Usage'],
- body: setDataUP,
- },
- alignment: [Align.Left, Align.Center],
+ gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_12']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
});
@@ -525,11 +546,12 @@ describe('โฝ๐ Gas Benchmark', () => {
false,
);
- // deploy a LSP7 token
+ // deploy a LSP8 NFT
lsp8Token = await new LSP8Mintable__factory(context.owner).deploy(
- 'Token',
- 'MTKN',
+ 'My NFT',
+ 'MNFT',
context.owner.address,
+ LSP8_TOKEN_ID_TYPES.NUMBER,
);
universalProfile1 = await new UniversalProfile__factory(context.owner).deploy(
@@ -543,10 +565,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Minting a LSP7Token to a UP (No Delegate) from an EOA',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_1']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('when minting LSP7Token to a EOA without data', async () => {
@@ -554,10 +574,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Minting a LSP7Token to an EOA from an EOA',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_2']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('when transferring LSP7Token from a UP to a UP without data', async () => {
@@ -575,10 +593,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Transferring an LSP7Token from a UP to another UP (No Delegate)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_3']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
@@ -600,10 +616,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Minting a LSP8Token to a UP (No Delegate) from an EOA',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_4']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('when minting LSP8Token to a EOA without data', async () => {
@@ -611,10 +625,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Minting a LSP8Token to an EOA from an EOA',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_5']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
it('when transferring LSP8Token from a UP to a UP without data', async () => {
@@ -632,20 +644,8 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- tokensUP.push([
- 'Transferring an LSP8Token from a UP to another UP (No Delegate)',
- receipt.gasUsed.toNumber().toString(),
- ]);
- });
- });
-
- after(async () => {
- UniversalProfileTokensTable = getMarkdownTable({
- table: {
- head: ['`Tokens` scenarios - ๐ UP Owner', 'โฝ Gas Usage'],
- body: tokensUP,
- },
- alignment: [Align.Left, Align.Center],
+ gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_6']['gas_cost'] =
+ receipt.gasUsed.toNumber();
});
});
});
@@ -679,9 +679,18 @@ describe('โฝ๐ Gas Benchmark', () => {
const deployedContracts = await setupProfileWithKeyManagerWithURD(context.accounts[2]);
aliceUP = deployedContracts[0] as UniversalProfile;
- // the function `setupKeyManager` gives ALL PERMISSIONS
- // to the owner as the first data key
- await setupKeyManager(context, [], []);
+ const lsp1Delegate: LSP1UniversalReceiverDelegateUP =
+ await new LSP1UniversalReceiverDelegateUP__factory(context.accounts[0]).deploy();
+
+ // the function `setupKeyManager` gives ALL PERMISSIONS to the owner as the first data key
+ // We also setup the following:
+ // - LSP1 Delegate (for registering LSP7 tokens + LSP8 NFTs)
+ // - LSP3Profile metadata (to test for updates)
+ await setupKeyManager(
+ context,
+ [ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate],
+ [lsp1Delegate.address],
+ );
// deploy a LSP7 token
lsp7MetaCoin = await new LSP7Mintable__factory(context.owner).deploy(
@@ -696,6 +705,7 @@ describe('โฝ๐ Gas Benchmark', () => {
'MetaNFT',
'MNF',
context.owner.address,
+ LSP8_TOKEN_ID_TYPES.NUMBER,
);
// mint some tokens to the UP
@@ -718,6 +728,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer LYX to an EOA',
receipt.gasUsed.toNumber().toString(),
]);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_1'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
it('transfers some LYXes to a UP', async () => {
@@ -731,6 +745,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer LYX to a UP',
receipt.gasUsed.toNumber().toString(),
]);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_2'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
it('transfers some tokens (LSP7) to an EOA (no data)', async () => {
@@ -755,6 +773,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer tokens (LSP7) to an EOA (no data)',
receipt.gasUsed.toNumber().toString(),
]);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_3'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
it('transfer some tokens (LSP7) to a UP (no data)', async () => {
@@ -779,6 +801,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer tokens (LSP7) to a UP (no data)',
receipt.gasUsed.toNumber().toString(),
]);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_4'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
it('transfer a NFT (LSP8) to a EOA (no data)', async () => {
@@ -803,6 +829,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer a NFT (LSP8) to a EOA (no data)',
receipt.gasUsed.toNumber().toString(),
]);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_5'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
it('transfer a NFT (LSP8) to a UP (no data)', async () => {
@@ -827,16 +857,10 @@ describe('โฝ๐ Gas Benchmark', () => {
'transfer a NFT (LSP8) to a UP (no data)',
receipt.gasUsed.toNumber().toString(),
]);
- });
- after(async () => {
- mainControllerExecuteTable = getMarkdownTable({
- table: {
- head: ['`execute` scenarios - ๐ main controller', 'โฝ Gas Usage'],
- body: casesExecuteMainController,
- },
- alignment: [Align.Left, Align.Center],
- });
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_6'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
});
@@ -876,6 +900,7 @@ describe('โฝ๐ Gas Benchmark', () => {
recipientEOA = context.accounts[1];
+ // UP receiving LYX, Tokens and NFT transfers
const deployedContracts = await setupProfileWithKeyManagerWithURD(context.accounts[2]);
aliceUP = deployedContracts[0] as UniversalProfile;
@@ -911,12 +936,14 @@ describe('โฝ๐ Gas Benchmark', () => {
'MetaNFT',
'MNF',
context.owner.address,
+ LSP8_TOKEN_ID_TYPES.NUMBER,
);
lsp8LyxPunks = await new LSP8Mintable__factory(context.owner).deploy(
'LyxPunks',
'LPK',
context.owner.address,
+ LSP8_TOKEN_ID_TYPES.UNIQUE_ID,
);
[
@@ -929,10 +956,15 @@ describe('โฝ๐ Gas Benchmark', () => {
});
});
+ const lsp1Delegate = await new LSP1UniversalReceiverDelegateUP__factory(
+ context.accounts[0],
+ ).deploy();
+
// prettier-ignore
await setupKeyManager(
context,
[
+ ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate,
ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferValueToOneAddress.address.substring(2),
ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferTwoTokens.address.substring(2),
ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferTwoNFTs.address.substring(2),
@@ -941,10 +973,11 @@ describe('โฝ๐ Gas Benchmark', () => {
ERC725YDataKeys.LSP6["AddressPermissions:AllowedCalls"] + canTransferTwoNFTs.address.substring(2),
],
[
+ lsp1Delegate.address,
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 +994,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 +1003,32 @@ 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']['KeyManager_owner']['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']['KeyManager_owner']['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 +1053,10 @@ 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']['KeyManager_owner']['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 +1081,10 @@ 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']['KeyManager_owner']['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 +1109,10 @@ 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']['KeyManager_owner']['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,16 +1137,10 @@ 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(),
]);
- });
- after(async () => {
- restrictedControllerExecuteTable = getMarkdownTable({
- table: {
- head: ['`execute` scenarios - ๐ restricted controller', 'โฝ Gas Usage'],
- body: casesExecuteRestrictedController,
- },
- alignment: [Align.Left, Align.Center],
- });
+ gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_6'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
});
});
});
@@ -1086,8 +1148,7 @@ describe('โฝ๐ Gas Benchmark', () => {
describe('`setData(...)` via Key Manager', () => {
let context: LSP6TestContext;
- let controllerCanSetData: SignerWithAddress,
- controllerCanSetDataAndAddController: SignerWithAddress;
+ let controllerToAddEditAndRemove: SignerWithAddress;
const allowedERC725YDataKeys = [
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key1')),
@@ -1102,45 +1163,38 @@ describe('โฝ๐ Gas Benchmark', () => {
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key10')),
];
+ // Fictional scenario of a NFT Marketplace dApp
+ const nftMarketplaceDataKeys = [
+ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - settings')),
+ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - followers')),
+ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - rewards')),
+ ];
+
before(async () => {
context = await buildLSP6TestContext();
- controllerCanSetData = context.accounts[1];
- controllerCanSetDataAndAddController = context.accounts[2];
+ controllerToAddEditAndRemove = context.accounts[1];
// prettier-ignore
const permissionKeys = [
- ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + context.owner.address.substring(2),
- ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetData.address.substring(2),
- ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetData.address.substring(2),
- ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetDataAndAddController.address.substring(2),
- ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetDataAndAddController.address.substring(2),
+ ERC725YDataKeys.LSP3.LSP3Profile,
ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000000",
- ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000001",
- ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000002",
];
- // // prettier-ignore
const permissionValues = [
- ALL_PERMISSIONS,
- PERMISSIONS.SETDATA,
- encodeCompactBytesArray(allowedERC725YDataKeys),
- combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.ADDCONTROLLER),
- encodeCompactBytesArray(allowedERC725YDataKeys),
+ // Set some JSONURL for LSP3Profile metadata to test gas cost of updating your profile details
+ '0x6f357c6a70546a2accab18748420b63c63b5af4cf710848ae83afc0c51dd8ad17fb5e8b3697066733a2f2f516d65637247656a555156587057347a53393438704e76636e51724a314b69416f4d36626466725663575a736e35',
ethers.utils.hexZeroPad(ethers.BigNumber.from(3).toHexString(), 16),
context.owner.address,
- controllerCanSetData.address,
- controllerCanSetDataAndAddController.address,
];
+ // The `context.owner` is given `ALL_PERMISSIONS` as the first data key through `setupKeyManager` method.
await setupKeyManager(context, permissionKeys, permissionValues);
});
describe('main controller (this browser extension) has SUPER_SETDATA ', () => {
- const benchmarkCasesSetDataMainController: Row[] = [];
-
- it('updates profile details (LSP3Profile metadata)', async () => {
+ it('Update profile details (LSP3Profile metadata)', async () => {
const dataKey = ERC725YDataKeys.LSP3['LSP3Profile'];
const dataValue =
'0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178';
@@ -1148,20 +1202,21 @@ describe('โฝ๐ Gas Benchmark', () => {
const tx = await context.universalProfile
.connect(context.owner)
.setData(dataKey, dataValue);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataMainController.push([
- 'updates profile details (LSP3Profile metadata)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_1'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it(`give permissions to a controller
- 1. increase AddressPermissions[] array length
- 2. put the controller address at AddressPermissions[index]
- 3. give the controller the permission SETDATA under AddressPermissions:Permissions:
+ it(`Give permissions to a controller
+ 1. increase \`AddressPermissions[]\` array length
+ 2. put the controller address at \`AddressPermissions[index]\`
+ 3. give the controller the permission \`SETDATA\` under \`AddressPermissions:Permissions:\`
+ 4. allow the controller to set 3x specific ERC725Y data keys under \`AddressPermissions:AllowedERC725YDataKeys:\`
`, async () => {
- const newController = context.accounts[3];
+ const newController = controllerToAddEditAndRemove;
const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)'](
ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
@@ -1172,6 +1227,7 @@ describe('โฝ๐ Gas Benchmark', () => {
ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2),
ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2),
];
// prettier-ignore
@@ -1179,6 +1235,11 @@ describe('โฝ๐ Gas Benchmark', () => {
ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).add(1).toHexString(), 16),
newController.address,
combinePermissions(PERMISSIONS.SETDATA),
+ encodeCompactBytesArray([
+ ERC725YDataKeys.LSP3.LSP3Profile,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'],
+ ])
];
const tx = await context.universalProfile
@@ -1189,60 +1250,19 @@ describe('โฝ๐ Gas Benchmark', () => {
expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues);
- benchmarkCasesSetDataMainController.push([
- 'give permissions to a controller (AddressPermissions[] + AddressPermissions[index] + AddressPermissions:Permissions:)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_2'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('restrict a controller to some specific ERC725Y Data Keys', async () => {
- const controllerToEdit = context.accounts[3];
-
- const allowedDataKeys = [
- ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 1')),
- ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 2')),
- ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 3')),
- ];
+ it('Update permissions of previous controller. Allow it now to `SUPER_SETDATA`', async () => {
+ const controllerToEdit = controllerToAddEditAndRemove;
- // prettier-ignore
const dataKey =
- ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerToEdit.address.substring(2)
-
- // prettier-ignore
- const dataValue = encodeCompactBytesArray([allowedDataKeys[0], allowedDataKeys[1], allowedDataKeys[2]])
-
- const tx = await context.universalProfile
- .connect(context.owner)
- .setData(dataKey, dataValue);
-
- const receipt = await tx.wait();
-
- expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue);
-
- benchmarkCasesSetDataMainController.push([
- 'restrict a controller to some specific ERC725Y Data Keys',
- receipt.gasUsed.toNumber().toString(),
- ]);
- });
-
- it('restrict a controller to interact only with 3x specific addresses', async () => {
- const controllerToEdit = context.accounts[3];
-
- const allowedAddresses = [
- context.accounts[4].address,
- context.accounts[5].address,
- context.accounts[6].address,
- ];
-
- // prettier-ignore
- const dataKey = ERC725YDataKeys.LSP6["AddressPermissions:AllowedCalls"] + controllerToEdit.address.substring(2)
+ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
+ controllerToEdit.address.substring(2);
- const dataValue = combineAllowedCalls(
- [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL],
- [allowedAddresses[0], allowedAddresses[1], allowedAddresses[2]],
- ['0xffffffff', '0xffffffff', '0xffffffff'],
- ['0xffffffff', '0xffffffff', '0xffffffff'],
- );
+ const dataValue = combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.SUPER_SETDATA);
const tx = await context.universalProfile
.connect(context.owner)
@@ -1252,18 +1272,18 @@ describe('โฝ๐ Gas Benchmark', () => {
expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue);
- benchmarkCasesSetDataMainController.push([
- 'restrict a controller to interact only with 3x specific addresses',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_3'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it(`remove a controller (its permissions + its address from the AddressPermissions[] array)
- 1. decrease AddressPermissions[] array length
- 2. remove the controller address at AddressPermissions[index]
- 3. set "0x" for the controller permissions under AddressPermissions:Permissions:
+ it(`Remove a controller
+ 1. decrease \`AddressPermissions[]\` array length
+ 2. remove the controller address at \`AddressPermissions[index]\`
+ 3. set \`0x\` for the controller permissions under \`AddressPermissions:Permissions:\`
+ 4. remove the Allowed ERC725Y Data Keys previously set for the controller under \`AddressPermissions:AllowedERC725YDataKeys:\`
`, async () => {
- const newController = context.accounts[3];
+ const newController = controllerToAddEditAndRemove;
const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)'](
ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
@@ -1274,6 +1294,7 @@ describe('โฝ๐ Gas Benchmark', () => {
ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2),
ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2),
];
// prettier-ignore
@@ -1281,6 +1302,7 @@ describe('โฝ๐ Gas Benchmark', () => {
ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16),
"0x",
"0x",
+ "0x",
];
const tx = await context.universalProfile
@@ -1289,13 +1311,12 @@ describe('โฝ๐ Gas Benchmark', () => {
const receipt = await tx.wait();
- benchmarkCasesSetDataMainController.push([
- 'remove a controller (its permissions + its address from the AddressPermissions[] array)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_4'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('write 5x LSP12 Issued Assets', async () => {
+ it('Write 5x new LSP12 Issued Assets', async () => {
// prettier-ignore
const issuedAssetsDataKeys = [
ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].length,
@@ -1327,196 +1348,492 @@ describe('โฝ๐ Gas Benchmark', () => {
issuedAssetsDataValues,
);
- benchmarkCasesSetDataMainController.push([
- 'write 5x LSP12 Issued Assets',
- receipt.gasUsed.toNumber().toString(),
- ]);
- });
-
- after(async () => {
- mainControllerSetDataTable = getMarkdownTable({
- table: {
- head: ['`setData` scenarios - ๐ main controller', 'โฝ Gas Usage'],
- body: benchmarkCasesSetDataMainController,
- },
- alignment: [Align.Left, Align.Center],
- });
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- });
- describe('a controller (EOA) can SETDATA, ADDCONTROLLER and on 10x AllowedERC725YKeys', () => {
- const benchmarkCasesSetDataRestrictedController: Row[] = [];
-
- it('`setData(bytes32,bytes)` -> updates 1x data key', async () => {
+ it('Updates 1x data key', async () => {
const dataKey = allowedERC725YDataKeys[5];
const dataValue = '0xaabbccdd';
const tx = await context.universalProfile
- .connect(controllerCanSetData)
+ .connect(context.owner)
.setData(dataKey, dataValue);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataRestrictedController.push([
- '`setData(bytes32,bytes)` -> updates 1x data key',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('`setData(bytes32[],bytes[])` -> updates 3x data keys (first x3)', async () => {
+ it('Updates 3x data keys (first x3)', async () => {
const dataKeys = allowedERC725YDataKeys.slice(0, 3);
const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
const tx = await context.universalProfile
- .connect(controllerCanSetData)
+ .connect(context.owner)
.setDataBatch(dataKeys, dataValues);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataRestrictedController.push([
- '`setData(bytes32[],bytes[])` -> updates 3x data keys (first x3)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_6'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('`setData(bytes32[],bytes[])` -> updates 3x data keys (middle x3)', async () => {
+ it('Update 3x data keys (middle x3)', async () => {
const dataKeys = allowedERC725YDataKeys.slice(3, 6);
const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
const tx = await context.universalProfile
- .connect(controllerCanSetData)
+ .connect(context.owner)
.setDataBatch(dataKeys, dataValues);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataRestrictedController.push([
- '`setData(bytes32[],bytes[])` -> updates 3x data keys (middle x3)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_7'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('`setData(bytes32[],bytes[])` -> updates 3x data keys (last x3)', async () => {
+ it('Update 3x data keys (last x3)', async () => {
const dataKeys = allowedERC725YDataKeys.slice(7, 10);
const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
const tx = await context.universalProfile
- .connect(controllerCanSetData)
+ .connect(context.owner)
.setDataBatch(dataKeys, dataValues);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataRestrictedController.push([
- '`setData(bytes32[],bytes[])` -> updates 3x data keys (last x3)',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_8'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
- it('`setData(bytes32[],bytes[])` -> updates 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index])', async () => {
+ it('Set 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index]) - 12 data keys in total', async () => {
+ const addressPermissionsArrayLength = ethers.BigNumber.from(
+ await context.universalProfile.getData(
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ ),
+ ).toNumber();
+
+ const newArrayLengthUint128Hex = ethers.utils.hexZeroPad(
+ ethers.BigNumber.from(addressPermissionsArrayLength).add(3).toHexString(),
+ 16,
+ );
+
+ // example of a dApp that set some logic
+ const compactBytesArrayAllowedERC725YDataKeys = encodeCompactBytesArray([
+ ...nftMarketplaceDataKeys,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'],
+ ]);
+
const dataKeys = [
- allowedERC725YDataKeys[0],
- allowedERC725YDataKeys[1],
+ nftMarketplaceDataKeys[0], // set the settings and followers to 0 to start (rewards are set later)
+ nftMarketplaceDataKeys[1],
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength}`,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength + 1}`,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength + 2}`,
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
context.accounts[3].address.substring(2),
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
context.accounts[4].address.substring(2),
ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
context.accounts[5].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[3].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[4].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[5].address.substring(2),
];
const dataValues = [
- '0xaabbccdd',
- '0xaabbccdd',
+ // user settings
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some default user settings to start')),
+ // followers count starts at 0
+ abiCoder.encode(['uint256'], [0]),
+ newArrayLengthUint128Hex,
+ context.accounts[3].address,
+ context.accounts[4].address,
+ context.accounts[5].address,
PERMISSIONS.SETDATA,
PERMISSIONS.SETDATA,
PERMISSIONS.SETDATA,
+ compactBytesArrayAllowedERC725YDataKeys,
+ compactBytesArrayAllowedERC725YDataKeys,
+ compactBytesArrayAllowedERC725YDataKeys,
];
const tx = await context.universalProfile
- .connect(controllerCanSetDataAndAddController)
+ .connect(context.owner)
.setDataBatch(dataKeys, dataValues);
+
const receipt = await tx.wait();
- benchmarkCasesSetDataRestrictedController.push([
- '`setData(bytes32[],bytes[])` -> updates 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index])',
- receipt.gasUsed.toNumber().toString(),
- ]);
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_9'][
+ 'main_controller'
+ ] = receipt.gasUsed.toNumber();
});
+ });
- after(async () => {
- restrictedControllerSetDataTable = getMarkdownTable({
- table: {
- head: ['`setData` scenarios - ๐ restricted controller', 'โฝ Gas Usage'],
- body: benchmarkCasesSetDataRestrictedController,
- },
- alignment: [Align.Left, Align.Center],
- });
+ describe('restricted controllers', () => {
+ let controllercanSetTwoDataKeys: SignerWithAddress,
+ controllerCanAddControllers: SignerWithAddress,
+ controllerCanEditPermissions: SignerWithAddress,
+ controllerCanSetTenDataKeys: SignerWithAddress,
+ controllerCanSetDataAndAddControllers: SignerWithAddress;
+
+ before(async () => {
+ context = await buildLSP6TestContext();
+
+ controllercanSetTwoDataKeys = context.accounts[1];
+ controllerCanAddControllers = context.accounts[2];
+ controllerCanEditPermissions = context.accounts[3];
+ controllerCanSetTenDataKeys = context.accounts[4];
+ controllerCanSetDataAndAddControllers = context.accounts[5];
+
+ controllerToAddEditAndRemove = context.accounts[6];
+
+ // prettier-ignore
+ const permissionKeys = [
+ ERC725YDataKeys.LSP3.LSP3Profile,
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000000",
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllercanSetTwoDataKeys.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllercanSetTwoDataKeys.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanAddControllers.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanEditPermissions.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetTenDataKeys.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetTenDataKeys.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetDataAndAddControllers.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetDataAndAddControllers.address.substring(2),
+ ];
+
+ const permissionValues = [
+ // Set some JSONURL for LSP3Profile metadata to test gas cost of updating your profile details
+ '0x6f357c6a70546a2accab18748420b63c63b5af4cf710848ae83afc0c51dd8ad17fb5e8b3697066733a2f2f516d65637247656a555156587057347a53393438704e76636e51724a314b69416f4d36626466725663575a736e35',
+ ethers.utils.hexZeroPad(ethers.BigNumber.from(6).toHexString(), 16),
+ context.owner.address,
+ PERMISSIONS.SETDATA,
+ encodeCompactBytesArray([
+ ERC725YDataKeys.LSP3.LSP3Profile,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ]),
+ PERMISSIONS.ADDCONTROLLER,
+ PERMISSIONS.EDITPERMISSIONS,
+ PERMISSIONS.SETDATA,
+ encodeCompactBytesArray(allowedERC725YDataKeys),
+ combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.ADDCONTROLLER),
+ encodeCompactBytesArray([
+ ...nftMarketplaceDataKeys,
+ ERC725YDataKeys.LSP3.LSP3Profile,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ]),
+ ];
+
+ // The `context.owner` is given `ALL_PERMISSIONS` as the first data key through `setupKeyManager` method.
+ await setupKeyManager(context, permissionKeys, permissionValues);
});
- });
- });
- });
- after(async () => {
- 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.
+ it('Update profile details (LSP3Profile metadata)', async () => {
+ const dataKey = ERC725YDataKeys.LSP3['LSP3Profile'];
+ const dataValue =
+ '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178';
+
+ const tx = await context.universalProfile
+ .connect(controllercanSetTwoDataKeys)
+ .setData(dataKey, dataValue);
+
+ const receipt = await tx.wait();
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_1'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
+
+ it(`Give permissions to a controller
+ 1. increase \`AddressPermissions[]\` array length
+ 2. put the controller address at \`AddressPermissions[index]\`
+ 3. give the controller the permission \`SETDATA\` under \`AddressPermissions:Permissions:\`
+ 4. allow the controller to set 3x specific ERC725Y data keys under \`AddressPermissions:AllowedERC725YDataKeys:\`
+ `, async () => {
+ const newController = controllerToAddEditAndRemove;
+
+ const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)'](
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ );
+
+ // prettier-ignore
+ const dataKeys = [
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2),
+ ];
+
+ // prettier-ignore
+ const dataValues = [
+ ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).add(1).toHexString(), 16),
+ newController.address,
+ combinePermissions(PERMISSIONS.SETDATA),
+ encodeCompactBytesArray([
+ ERC725YDataKeys.LSP3.LSP3Profile,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'],
+ ])
+ ];
+
+ const tx = await context.universalProfile
+ .connect(controllerCanAddControllers)
+ .setDataBatch(dataKeys, dataValues);
+
+ const receipt = await tx.wait();
+
+ expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_2'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
+
+ it('Update permissions of previous controller. Allow it now to `SUPER_SETDATA`', async () => {
+ const controllerToEdit = controllerToAddEditAndRemove;
+
+ const dataKey =
+ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
+ controllerToEdit.address.substring(2);
+
+ const dataValue = combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.SUPER_SETDATA);
+
+ const tx = await context.universalProfile
+ .connect(controllerCanEditPermissions)
+ .setData(dataKey, dataValue);
+
+ const receipt = await tx.wait();
+
+ expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue);
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_3'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
+
+ it(`Remove a controller
+ 1. decrease \`AddressPermissions[]\` array length
+ 2. remove the controller address at \`AddressPermissions[index]\`
+ 3. set \`0x\` for the controller permissions under \`AddressPermissions:Permissions:\`
+ 4. remove the Allowed ERC725Y Data Keys previously set for the controller under \`AddressPermissions:AllowedERC725YDataKeys:\`
+ `, async () => {
+ const newController = controllerToAddEditAndRemove;
-
-โฝ๐ See Gas Benchmark report of Using UniversalProfile owned by an EOA
+ const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)'](
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ );
-### ๐ \`execute\` scenarios
+ // prettier-ignore
+ const dataKeys = [
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].length,
+ ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1), 16).substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2),
+ ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2),
+ ];
-${UniversalProfileExecuteTable}
+ // prettier-ignore
+ const dataValues = [
+ ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16),
+ "0x",
+ "0x",
+ "0x",
+ ];
-### ๐๏ธ \`setData\` scenarios
+ const tx = await context.universalProfile
+ .connect(controllerCanEditPermissions)
+ .setDataBatch(dataKeys, dataValues);
-${UniversalProfileSetDataTable}
+ const receipt = await tx.wait();
-### ๐๏ธ \`Tokens\` scenarios
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_4'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-${UniversalProfileTokensTable}
+ it('Write 5x new LSP12 Issued Assets', async () => {
+ // prettier-ignore
+ const issuedAssetsDataKeys = [
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].length,
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000000",
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000001",
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000002",
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000003",
+ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000004",
+ ];
+ // these are just random placeholder values
+ // they should be replaced with actual token contract address
+ const issuedAssetsDataValues = [
+ '0x0000000000000000000000000000000000000000000000000000000000000005',
+ context.accounts[5].address,
+ context.accounts[6].address,
+ context.accounts[7].address,
+ context.accounts[8].address,
+ context.accounts[9].address,
+ ];
+ const tx = await context.universalProfile
+ .connect(controllercanSetTwoDataKeys)
+ .setDataBatch(issuedAssetsDataKeys, issuedAssetsDataValues);
-## ๐ Notes
+ const receipt = await tx.wait();
-- The \`execute\` and \`setData\` scenarios are executed on a fresh UniversalProfile smart contract, deployed as standard contracts (not as proxy behind a base contract implementation).
+ expect(await context.universalProfile.getDataBatch(issuedAssetsDataKeys)).to.deep.equal(
+ issuedAssetsDataValues,
+ );
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-
+ it('Updates 1x data key', async () => {
+ const dataKey = allowedERC725YDataKeys[5];
+ const dataValue = '0xaabbccdd';
+ const tx = await context.universalProfile
+ .connect(controllerCanSetTenDataKeys)
+ .setData(dataKey, dataValue);
-
-โฝ๐ See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager
+ const receipt = await tx.wait();
-This document contains the gas usage for common interactions and scenarios when using UniversalProfile smart contracts.
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-### ๐ \`execute\` scenarios
+ it('Updates 3x data keys (first x3)', async () => {
+ const dataKeys = allowedERC725YDataKeys.slice(0, 3);
+ const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
-#### ๐ unrestricted controller
+ const tx = await context.universalProfile
+ .connect(controllerCanSetTenDataKeys)
+ .setDataBatch(dataKeys, dataValues);
-${mainControllerExecuteTable}
+ const receipt = await tx.wait();
-#### ๐ restricted controller
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_6'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-${restrictedControllerExecuteTable}
+ it('Update 3x data keys (middle x3)', async () => {
+ const dataKeys = allowedERC725YDataKeys.slice(3, 6);
+ const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
-### ๐๏ธ \`setData\` scenarios
+ const tx = await context.universalProfile
+ .connect(controllerCanSetTenDataKeys)
+ .setDataBatch(dataKeys, dataValues);
-#### ๐ unrestricted controller
+ const receipt = await tx.wait();
-${mainControllerSetDataTable}
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_7'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-#### ๐ restricted controller
+ it('Update 3x data keys (last x3)', async () => {
+ const dataKeys = allowedERC725YDataKeys.slice(7, 10);
+ const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd'];
-${restrictedControllerSetDataTable}
+ const tx = await context.universalProfile
+ .connect(controllerCanSetTenDataKeys)
+ .setDataBatch(dataKeys, dataValues);
+ const receipt = await tx.wait();
-## ๐ Notes
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_8'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
-- 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).
+ it('Set 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index]) - 12 data keys in total', async () => {
+ const addressPermissionsArrayLength = ethers.BigNumber.from(
+ await context.universalProfile.getData(
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ ),
+ ).toNumber();
+ const newArrayLengthUint128Hex = ethers.utils.hexZeroPad(
+ ethers.BigNumber.from(addressPermissionsArrayLength).add(3).toHexString(),
+ 16,
+ );
-
-`;
- const file = 'benchmark.md';
+ // example of a dApp that set some logic
+ const compactBytesArrayAllowedERC725YDataKeys = encodeCompactBytesArray([
+ ...nftMarketplaceDataKeys,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index,
+ ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'],
+ ]);
- fs.writeFileSync(file, markdown);
+ const dataKeys = [
+ nftMarketplaceDataKeys[0], // set the settings and followers to 0 to start (rewards are set later)
+ nftMarketplaceDataKeys[1],
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].length,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength}`,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength + 1}`,
+ ERC725YDataKeys.LSP6['AddressPermissions[]'].index +
+ `0000000000000000000000000000000${addressPermissionsArrayLength + 2}`,
+ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
+ context.accounts[7].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
+ context.accounts[8].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] +
+ context.accounts[9].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[7].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[8].address.substring(2),
+ ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] +
+ context.accounts[9].address.substring(2),
+ ];
+
+ const dataValues = [
+ // user settings
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some default user settings to start')),
+ // followers count starts at 0
+ abiCoder.encode(['uint256'], [0]),
+ newArrayLengthUint128Hex,
+ context.accounts[7].address,
+ context.accounts[8].address,
+ context.accounts[9].address,
+ PERMISSIONS.SETDATA,
+ PERMISSIONS.SETDATA,
+ PERMISSIONS.SETDATA,
+ compactBytesArrayAllowedERC725YDataKeys,
+ compactBytesArrayAllowedERC725YDataKeys,
+ compactBytesArrayAllowedERC725YDataKeys,
+ ];
+
+ const tx = await context.universalProfile
+ .connect(controllerCanSetDataAndAddControllers)
+ .setDataBatch(dataKeys, dataValues);
+
+ const receipt = await tx.wait();
+
+ gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_9'][
+ 'restricted_controller'
+ ] = receipt.gasUsed.toNumber();
+ });
+ });
+ });
});
});