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(); + }); + }); + }); }); });