From 445be6c2fb68318ffd3bdbe0c35977b26d9adaf9 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Fri, 22 Sep 2023 13:54:43 +0100 Subject: [PATCH] build: write Hardhat task for gas benchmark --- hardhat.config.ts | 3 + scripts/ci/gas_benchmark.ts | 207 ++++++ scripts/ci/gas_benchmark_template.json | 204 ++++++ tests/Benchmark.test.ts | 932 ++++++++++++++++--------- 4 files changed, 1006 insertions(+), 340 deletions(-) create mode 100644 scripts/ci/gas_benchmark.ts create mode 100644 scripts/ci/gas_benchmark_template.json diff --git a/hardhat.config.ts b/hardhat.config.ts index 78aeec4d1..175e5dad2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,7 +18,10 @@ import '@nomicfoundation/hardhat-toolbox'; import 'hardhat-packager'; import 'hardhat-contract-sizer'; import 'hardhat-deploy'; + +// custom built hardhat plugins for CI import './scripts/ci/docs-generate'; +import './scripts/ci/gas_benchmark'; // Typescript types for web3.js import '@nomiclabs/hardhat-web3'; diff --git a/scripts/ci/gas_benchmark.ts b/scripts/ci/gas_benchmark.ts new file mode 100644 index 000000000..9272b9ecf --- /dev/null +++ b/scripts/ci/gas_benchmark.ts @@ -0,0 +1,207 @@ +import fs from 'fs'; +import { task } from 'hardhat/config'; +import { Align, getMarkdownTable, Row } from 'markdown-table-ts'; + +task('gas-benchmark', 'Benchmark gas usage of the smart contracts based on predefined scenarios') + .addParam( + 'compare', + 'The `.json` file that contains the gas costs of the currently compiled contracts (e.g: current working branch)', + ) + .addParam( + 'against', + 'The `.json` file that contains the gas costs to compare against (e.g: the `develop` branch)', + ) + .setAction(async function (args, hre, runSuper) { + const currentBenchmark = JSON.parse(fs.readFileSync(args.compare, 'utf8')); + const baseBenchmark = JSON.parse(fs.readFileSync(args.against, 'utf8')); + + const casesEOAExecute: Row[] = []; + const casesEOASetData: Row[] = []; + const casesEOAToken: Row[] = []; + + const casesExecute: Row[] = []; + const casesSetData: Row[] = []; + + const numberWithCommas = (value) => { + return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + }; + + const displayGasDiffText = (gasDiff) => { + const gasDiffNumber = parseInt(gasDiff); + + let emoji; + + if (gasDiffNumber === 0) { + emoji = ''; + } else { + emoji = parseInt(gasDiff) < 0 ? '✅' : '❌'; + } + + return ` ( ${numberWithCommas(gasDiff)} ${emoji})`; + }; + + // EOA - Execute + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['execute'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['execute'][key]['gas_cost']; + + casesEOAExecute.push([ + value['description'], + numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff), + ]); + } + + const generatedExecuteEOATable = getMarkdownTable({ + table: { + head: ['`execute` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'], + body: casesEOAExecute, + }, + alignment: [Align.Left], + }); + + // EOA - Set Data + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['setData'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['setData'][key]['gas_cost']; + + casesEOASetData.push([ + value['description'], + numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff), + ]); + } + + const generatedSetDataEOATable = getMarkdownTable({ + table: { + head: ['`setData` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'], + body: casesEOASetData, + }, + alignment: [Align.Left], + }); + + // EOA - Token + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['tokens'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['tokens'][key]['gas_cost']; + + casesEOAToken.push([ + value['description'], + numberWithCommas(value['gas_cost']) + displayGasDiffText(gasDiff), + ]); + } + + const generatedTokenEOATable = getMarkdownTable({ + table: { + head: ['`execute` scenarios - UP Owner as EOA 🔑', '⛽ Gas change'], + body: casesEOAToken, + }, + alignment: [Align.Left], + }); + + // Key Manager - Execute + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['KeyManager_owner']['execute'], + )) { + const gasDiffMainController = + value['main_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['main_controller']; + + const gasDiffRestrictedController = + value['restricted_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['restricted_controller']; + + casesExecute.push([ + value['description'], + numberWithCommas(value['main_controller']) + displayGasDiffText(gasDiffMainController), + numberWithCommas(value['restricted_controller']) + + displayGasDiffText(gasDiffRestrictedController), + ]); + } + + const generatedExecuteTable = getMarkdownTable({ + table: { + head: ['`execute` scenarios', '👑 main controller', '🛃 restricted controller'], + body: casesExecute, + }, + alignment: [Align.Left], + }); + + // Key Manager - Set Data + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['KeyManager_owner']['setData'], + )) { + const gasDiffMainController = + value['main_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['main_controller']; + + const gasDiffRestrictedController = + value['restricted_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['restricted_controller']; + + casesSetData.push([ + value['description'], + numberWithCommas(value['main_controller']) + displayGasDiffText(gasDiffMainController), + numberWithCommas(value['restricted_controller']) + + displayGasDiffText(gasDiffRestrictedController), + ]); + } + + const generatedSetDataTable = getMarkdownTable({ + table: { + head: ['`setData` scenarios', '👑 main controller', '🛃 restricted controller'], + body: casesSetData, + }, + alignment: [Align.Left], + }); + + const file = 'gas_benchmark.md'; + + const markdown = ` +👋 Hello +⛽ I am the Gas Bot Reporter. I keep track of the gas costs of common interactions using Universal Profiles 🆙 ! +📊 Here is a summary of the gas cost with the code introduced by this PR. + +
+ +⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager + +This document contains the gas usage for common interactions and scenarios when using UniversalProfile smart contracts. + +> **Note:** the \`execute\` and \`setData\` scenarios are executed on a fresh UniversalProfile and LSP6KeyManager smart contracts, deployed as standard contracts (not as proxy behind a base contract implementation). + +### 🔀 \`execute\` scenarios + +${generatedExecuteTable} + +### 🗄️ \`setData\` scenarios + +${generatedSetDataTable} + +
+ +
+ +⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager + +### 🔀 \`execute\` scenarios + +${generatedExecuteEOATable} + +### 🗄️ \`setData\` scenarios + +${generatedSetDataEOATable} + +### 🗄️ \`Tokens\` scenarios + +${generatedTokenEOATable} + +
+ `; + + fs.writeFileSync(file, markdown); + }); diff --git a/scripts/ci/gas_benchmark_template.json b/scripts/ci/gas_benchmark_template.json new file mode 100644 index 000000000..8422095bf --- /dev/null +++ b/scripts/ci/gas_benchmark_template.json @@ -0,0 +1,204 @@ +{ + "deployment_costs": { + "Universal Profile": "", + "Key Manager": "", + "LSP1 Delegate UP": "", + "LSP7 Digital Asset": "", + "LSP8 Identifiable Digital Asset": "" + }, + "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": 80484, + "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..5932452ac 100644 --- a/tests/Benchmark.test.ts +++ b/tests/Benchmark.test.ts @@ -5,6 +5,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Align, getMarkdownTable, Row } from 'markdown-table-ts'; import { + LSP1UniversalReceiverDelegateUP__factory, LSP6KeyManager__factory, LSP7Mintable, LSP7Mintable__factory, @@ -24,7 +25,12 @@ import { } 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 = { @@ -67,13 +73,17 @@ let UniversalProfileSetDataTable; let UniversalProfileExecuteTable; let UniversalProfileTokensTable; -let mainControllerExecuteTable; -let restrictedControllerExecuteTable; +describe('⛽📊 Gas Benchmark', () => { + let gasBenchmark; -let mainControllerSetDataTable; -let restrictedControllerSetDataTable; + before('setup benchmark file', async () => { + gasBenchmark = JSON.parse(fs.readFileSync('./scripts/ci/gas_benchmark_template.json', 'utf8')); + }); + + after(async () => { + fs.writeFileSync('gas_benchmark_result.json', JSON.stringify(gasBenchmark, null, 2)); + }); -describe('⛽📊 Gas Benchmark', () => { describe('UniversalProfile', () => { let context: UniversalProfileContext; const executeUP: Row[] = []; @@ -98,10 +108,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 +124,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 +140,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,10 +156,8 @@ 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(); }); }); @@ -198,10 +200,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 +220,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 +244,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 +269,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 +289,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 +301,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 +313,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 +325,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 +337,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 +352,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 +366,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 +387,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 +402,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 +423,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 +446,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 +469,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(); }); }); }); @@ -543,10 +508,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 +517,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 +536,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 +559,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 +568,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,10 +587,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(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_6']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); @@ -679,9 +632,19 @@ 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 = 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( @@ -718,6 +681,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 +698,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 +726,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 +754,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 +782,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 +810,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 +853,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; @@ -929,10 +907,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 +924,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 +945,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 +954,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 +1004,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 +1032,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 +1060,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 +1088,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 +1099,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 +1114,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 +1153,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 +1178,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 +1186,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 +1201,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]; + it('Update permissions of previous controller. Allow it now to `SUPER_SETDATA`', async () => { + const controllerToEdit = controllerToAddEditAndRemove; - 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')), - ]; - - // 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 +1223,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 +1245,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 +1253,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 +1262,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 +1299,476 @@ 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 () => { - const dataKey = allowedERC725YDataKeys[5]; - const dataValue = '0xaabbccdd'; - const tx = await context.universalProfile - .connect(controllerCanSetData) - .setData(dataKey, dataValue); - const receipt = await tx.wait(); - - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32,bytes)` -> updates 1x data key', - receipt.gasUsed.toNumber().toString(), - ]); - }); - - it('`setData(bytes32[],bytes[])` -> updates 3x data keys (first x3)', async () => { + it('Update 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, + controllerCanSetDataAndAddController: SignerWithAddress; + + let controllerToAddEditAndRemove: SignerWithAddress; + + before("Setup restricted controllers' permissions", async () => { + context = await buildLSP6TestContext(); + + controllerCanSetTwoDataKeys = context.accounts[1]; // case 1 and 5 + controllerCanAddControllers = context.accounts[2]; // case 2 + controllerCanEditPermissions = context.accounts[3]; // case 3 and 4 + controllerCanSetTenDataKeys = context.accounts[4]; // case 6, 7 and 8 + controllerCanSetDataAndAddController = context.accounts[5]; // case 9 + + // for test scenarios + controllerToAddEditAndRemove = context.accounts[6]; + + // prettier-ignore + const permissionDataKeys = [ + ERC725YDataKeys.LSP3.LSP3Profile, + 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"] + controllerCanSetDataAndAddController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetDataAndAddController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions[]"].length, + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000000", + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000001", + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000002", + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000003", + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000004", + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000005", + ]; + + const permissionDataValues = [ + // Set some JSONURL for LSP3Profile metadata to test gas cost of updating your profile details + '0x6f357c6a70546a2accab18748420b63c63b5af4cf710848ae83afc0c51dd8ad17fb5e8b3697066733a2f2f516d65637247656a555156587057347a53393438704e76636e51724a314b69416f4d36626466725663575a736e35', + 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.LSP12['LSP12IssuedAssets[]'].index, + ]), + ethers.utils.hexZeroPad(ethers.BigNumber.from(6).toHexString(), 16), + context.owner.address, + controllerCanSetTwoDataKeys.address, + controllerCanAddControllers.address, + controllerCanEditPermissions.address, + controllerCanSetTenDataKeys.address, + controllerCanSetDataAndAddController.address, + ]; + + // The `context.owner` is given `ALL_PERMISSIONS` as the first data key through `setupKeyManager` method. + await setupKeyManager(context, permissionDataKeys, permissionDataValues); }); - }); - }); - }); - 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'; -
-⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an EOA + const tx = await context.universalProfile + .connect(controllerCanSetTwoDataKeys) + .setData(dataKey, dataValue); -### 🔀 \`execute\` scenarios + const receipt = await tx.wait(); -${UniversalProfileExecuteTable} + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_1'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); -### 🗄️ \`setData\` scenarios + 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; -${UniversalProfileSetDataTable} + const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ); -### 🗄️ \`Tokens\` scenarios + // 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), + ]; -${UniversalProfileTokensTable} + // 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(); -## 📝 Notes + expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues); -- 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). + 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); -
-⛽📊 See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager + const tx = await context.universalProfile + .connect(controllerCanEditPermissions) + .setData(dataKey, dataValue); -This document contains the gas usage for common interactions and scenarios when using UniversalProfile smart contracts. + const receipt = await tx.wait(); -### 🔀 \`execute\` scenarios + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); -#### 👑 unrestricted controller + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_3'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); -${mainControllerExecuteTable} + 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; -#### 🛃 restricted controller + const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ); -${restrictedControllerExecuteTable} + const arrayIndexToRemove = ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1); -### 🗄️ \`setData\` scenarios + // prettier-ignore + const dataKeys = [ + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + ethers.utils.hexZeroPad(arrayIndexToRemove.toHexString(), 16).substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + newController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + newController.address.substring(2), + ]; -#### 👑 unrestricted controller + // prettier-ignore + const dataValues = [ + ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16), + "0x", + "0x", + "0x", + ]; -${mainControllerSetDataTable} + const tx = await context.universalProfile + .connect(controllerCanEditPermissions) + .setDataBatch(dataKeys, dataValues); -#### 🛃 restricted controller + const receipt = await tx.wait(); -${restrictedControllerSetDataTable} + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_4'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + 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", + ]; -## 📝 Notes + // 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); -- 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). + const receipt = await tx.wait(); + expect(await context.universalProfile.getDataBatch(issuedAssetsDataKeys)).to.deep.equal( + issuedAssetsDataValues, + ); -
-`; - const file = 'benchmark.md'; + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + it('Update 3x data keys (first x3)', async () => { + const dataKeys = allowedERC725YDataKeys.slice(0, 3); + const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; + + const tx = await context.universalProfile + .connect(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_6'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + 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(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_7'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); - fs.writeFileSync(file, markdown); + 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(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_8'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + 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 = [ + 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(controllerCanSetDataAndAddController) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_9'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + }); + }); }); });