From 959a81a874a26facd6c9db748036610db1725db9 Mon Sep 17 00:00:00 2001 From: Laia Soler Date: Thu, 5 Dec 2024 12:21:02 +0100 Subject: [PATCH 1/3] add tool to change minDelay timelock --- tools/changeDelayTimelock/.gitignore | 2 + tools/changeDelayTimelock/README.md | 42 +++++ .../changeDelayTimelock.ts | 176 ++++++++++++++++++ .../change_delay_timelock.json.example | 13 ++ 4 files changed, 233 insertions(+) create mode 100644 tools/changeDelayTimelock/.gitignore create mode 100644 tools/changeDelayTimelock/README.md create mode 100644 tools/changeDelayTimelock/changeDelayTimelock.ts create mode 100644 tools/changeDelayTimelock/change_delay_timelock.json.example diff --git a/tools/changeDelayTimelock/.gitignore b/tools/changeDelayTimelock/.gitignore new file mode 100644 index 00000000..833f4756 --- /dev/null +++ b/tools/changeDelayTimelock/.gitignore @@ -0,0 +1,2 @@ +change_delay_output.json +change_delay_timelock.json \ No newline at end of file diff --git a/tools/changeDelayTimelock/README.md b/tools/changeDelayTimelock/README.md new file mode 100644 index 00000000..2d354468 --- /dev/null +++ b/tools/changeDelayTimelock/README.md @@ -0,0 +1,42 @@ +# Change minDelay PolygonZkEVMTimelock +Script to change `minDelay` from `PolygonZkEVMTimelock` + +## Install +``` +npm i +``` + +## Setup +- Config file `change_delay_timelock.json`: + - `timelockContractAddress`: timelock contract address + - `newMinDelay`: new `minDelay` + - `timeLockDelay`: timelockDelay (by defalult `timeLockDelay` == current `minDelay`) + - `timelockSalt(optional)`: timelock salt + - `predecessor(optional)`: timelock predecessor + - `deployerPvtKey`: private key deployer + - First option will load `deployerPvtKey`. Otherwise, `process.env.MNEMONIC` will be loaded from the `.env` file + - `maxFeePerGas`: set custom gas + - `maxPriorityFeePerGas`: set custom gas + - `multiplierGas`: set custom gas + - `sendSchedule`: true to send schedule tx + - `sendExecute`: true to send execute tx +- A network should be selected when running the script + - examples: `--network sepolia` or `--network mainnet` + - This uses variables set in `hardhat.config.ts` + - Which uses some environment variables that should be set in `.env` +> All paths are from root repository + +## Usage +> All commands are done from root repository. + +- Copy configuration file: +``` +cp ./tools/changeDelayTimelock/change_delay_timelock.json.example ./tools/changeDelayTimelock/change_delay_timelock.json +``` +- Set your parameters +- Run tool: +``` +npx hardhat run ./tools/changeDelayTimelock/changeDelayTimelock.ts --network sepolia +``` + + diff --git a/tools/changeDelayTimelock/changeDelayTimelock.ts b/tools/changeDelayTimelock/changeDelayTimelock.ts new file mode 100644 index 00000000..510d2a76 --- /dev/null +++ b/tools/changeDelayTimelock/changeDelayTimelock.ts @@ -0,0 +1,176 @@ +/* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if */ +/* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ +import {expect} from "chai"; +import path = require("path"); +import fs = require("fs"); + +import * as dotenv from "dotenv"; +dotenv.config({path: path.resolve(__dirname, "../../../.env")}); +import {ethers, network, upgrades} from "hardhat"; +import { PolygonZkEVMTimelock } from "../../typechain-types"; + +const parameters = require("./change_delay_timelock.json"); +const pathOutputJson = path.resolve(__dirname, "./change_delay_output.json"); + +async function main() { + + const outputJson = {} as any; + + // Load provider + let currentProvider = ethers.provider; + if (parameters.multiplierGas || parameters.maxFeePerGas) { + if (process.env.HARDHAT_NETWORK !== "hardhat") { + currentProvider = ethers.getDefaultProvider( + `https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}` + ) as any; + if (parameters.maxPriorityFeePerGas && parameters.maxFeePerGas) { + console.log( + `Hardcoded gas used: MaxPriority${parameters.maxPriorityFeePerGas} gwei, MaxFee${parameters.maxFeePerGas} gwei` + ); + const FEE_DATA = new ethers.FeeData( + null, + ethers.parseUnits(parameters.maxFeePerGas, "gwei"), + ethers.parseUnits(parameters.maxPriorityFeePerGas, "gwei") + ); + + currentProvider.getFeeData = async () => FEE_DATA; + } else { + console.log("Multiplier gas used: ", parameters.multiplierGas); + async function overrideFeeData() { + const feedata = await ethers.provider.getFeeData(); + return new ethers.FeeData( + null, + ((feedata.maxFeePerGas as bigint) * BigInt(parameters.multiplierGas)) / 1000n, + ((feedata.maxPriorityFeePerGas as bigint) * BigInt(parameters.multiplierGas)) / 1000n + ); + } + currentProvider.getFeeData = overrideFeeData; + } + } + } + + // Load deployer + let deployer; + if (parameters.deployerPvtKey) { + deployer = new ethers.Wallet(parameters.deployerPvtKey, currentProvider); + } else if (process.env.MNEMONIC) { + deployer = ethers.HDNodeWallet.fromMnemonic( + ethers.Mnemonic.fromPhrase(process.env.MNEMONIC), + "m/44'/60'/0'/0/0" + ).connect(currentProvider); + } else { + [deployer] = await ethers.getSigners(); + } + + + const timelockContractFactory = await ethers.getContractFactory("PolygonZkEVMTimelock", deployer); + const timelockContract = (await timelockContractFactory.attach( + parameters.timelockContractAddress + )) as PolygonZkEVMTimelock; + + console.log("#######################\n"); + console.log("timelockContract address: ", timelockContract.target) + console.log("#######################\n"); + + const timelockDelay = parameters.timeLockDelay ? parameters.timeLockDelay : Number(await timelockContract.getMinDelay()); + const salt = parameters.timelockSalt || ethers.ZeroHash; + const predecessor = parameters.predecessor || ethers.ZeroHash; + + const operation = genOperation( + parameters.timelockContractAddress, + 0, // value + timelockContract.interface.encodeFunctionData( + 'updateDelay', + [parameters.newMinDelay], + ), + predecessor, // predecessor + salt // salt + ); + + // Schedule operation + const scheduleData = timelockContractFactory.interface.encodeFunctionData("schedule", [ + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + timelockDelay, + ]); + + // Execute operation + const executeData = timelockContractFactory.interface.encodeFunctionData("execute", [ + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + ]); + + console.log("timelockDelay: ", timelockDelay) + console.log({scheduleData}); + console.log({executeData}); + + outputJson.scheduleData = scheduleData; + outputJson.executeData = executeData; + outputJson.minDelay = timelockDelay; + outputJson.functionData = { + function: 'updateDelay', + parameters: parameters.newMinDelay + } + + if(parameters.sendSchedule) { + const txScheduled = await timelockContract.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + timelockDelay, + ); + await txScheduled.wait(); + console.log("SEND SCHEDULE") + } + if (parameters.sendSchedule && parameters.sendExecute) { + await wait(timelockDelay); + } + if(parameters.sendExecute) { + const txExecute = await timelockContract.execute( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt + ); + await txExecute.wait(); + console.log("SEND EXECUTE") + console.log("newMinDelay: ", await timelockContract.getMinDelay()) + } + + await fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); +} + +// OZ test functions +function genOperation(target: any, value: any, data: any, predecessor: any, salt: any) { + const abiEncoded = ethers.AbiCoder.defaultAbiCoder().encode( + ["address", "uint256", "bytes", "uint256", "bytes32"], + [target, value, data, predecessor, salt] + ); + const id = ethers.keccak256(abiEncoded); + return { + id, + target, + value, + data, + predecessor, + salt, + }; +} + +function wait(seconds: number): Promise { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/tools/changeDelayTimelock/change_delay_timelock.json.example b/tools/changeDelayTimelock/change_delay_timelock.json.example new file mode 100644 index 00000000..cfb0d70b --- /dev/null +++ b/tools/changeDelayTimelock/change_delay_timelock.json.example @@ -0,0 +1,13 @@ +{ + "timelockContractAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "newMinDelay": 1800, + "timeLockDelay": "", + "predecessor": "", + "timelockSalt": "", + "deployerPvtKey": "", + "maxFeePerGas": "", + "maxPriorityFeePerGas": "", + "multiplierGas": "", + "sendSchedule": false, + "sendExecute": false +} \ No newline at end of file From 8fa4f45c1f6df7efd3f183cc206124040802001c Mon Sep 17 00:00:00 2001 From: Laia Soler Date: Thu, 5 Dec 2024 12:41:48 +0100 Subject: [PATCH 2/3] review tool change minDelay --- tools/changeDelayTimelock/.gitignore | 2 +- tools/changeDelayTimelock/README.md | 9 +- .../changeDelayTimelock.ts | 84 +------------------ .../change_delay_timelock.json.example | 8 +- 4 files changed, 6 insertions(+), 97 deletions(-) diff --git a/tools/changeDelayTimelock/.gitignore b/tools/changeDelayTimelock/.gitignore index 833f4756..bcbd021a 100644 --- a/tools/changeDelayTimelock/.gitignore +++ b/tools/changeDelayTimelock/.gitignore @@ -1,2 +1,2 @@ -change_delay_output.json +change_delay_output-* change_delay_timelock.json \ No newline at end of file diff --git a/tools/changeDelayTimelock/README.md b/tools/changeDelayTimelock/README.md index 2d354468..7df7878a 100644 --- a/tools/changeDelayTimelock/README.md +++ b/tools/changeDelayTimelock/README.md @@ -13,13 +13,6 @@ npm i - `timeLockDelay`: timelockDelay (by defalult `timeLockDelay` == current `minDelay`) - `timelockSalt(optional)`: timelock salt - `predecessor(optional)`: timelock predecessor - - `deployerPvtKey`: private key deployer - - First option will load `deployerPvtKey`. Otherwise, `process.env.MNEMONIC` will be loaded from the `.env` file - - `maxFeePerGas`: set custom gas - - `maxPriorityFeePerGas`: set custom gas - - `multiplierGas`: set custom gas - - `sendSchedule`: true to send schedule tx - - `sendExecute`: true to send execute tx - A network should be selected when running the script - examples: `--network sepolia` or `--network mainnet` - This uses variables set in `hardhat.config.ts` @@ -36,7 +29,7 @@ cp ./tools/changeDelayTimelock/change_delay_timelock.json.example ./tools/change - Set your parameters - Run tool: ``` -npx hardhat run ./tools/changeDelayTimelock/changeDelayTimelock.ts --network sepolia +npx hardhat run ./tools/changeDelayTimelock/changeDelayTimelock.ts --network localhost ``` diff --git a/tools/changeDelayTimelock/changeDelayTimelock.ts b/tools/changeDelayTimelock/changeDelayTimelock.ts index 510d2a76..872d6782 100644 --- a/tools/changeDelayTimelock/changeDelayTimelock.ts +++ b/tools/changeDelayTimelock/changeDelayTimelock.ts @@ -10,60 +10,14 @@ import {ethers, network, upgrades} from "hardhat"; import { PolygonZkEVMTimelock } from "../../typechain-types"; const parameters = require("./change_delay_timelock.json"); -const pathOutputJson = path.resolve(__dirname, "./change_delay_output.json"); +const dateStr = new Date().toISOString(); +const pathOutputJson = path.resolve(__dirname, `./change_delay_output-${dateStr}.json`); async function main() { const outputJson = {} as any; - // Load provider - let currentProvider = ethers.provider; - if (parameters.multiplierGas || parameters.maxFeePerGas) { - if (process.env.HARDHAT_NETWORK !== "hardhat") { - currentProvider = ethers.getDefaultProvider( - `https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}` - ) as any; - if (parameters.maxPriorityFeePerGas && parameters.maxFeePerGas) { - console.log( - `Hardcoded gas used: MaxPriority${parameters.maxPriorityFeePerGas} gwei, MaxFee${parameters.maxFeePerGas} gwei` - ); - const FEE_DATA = new ethers.FeeData( - null, - ethers.parseUnits(parameters.maxFeePerGas, "gwei"), - ethers.parseUnits(parameters.maxPriorityFeePerGas, "gwei") - ); - - currentProvider.getFeeData = async () => FEE_DATA; - } else { - console.log("Multiplier gas used: ", parameters.multiplierGas); - async function overrideFeeData() { - const feedata = await ethers.provider.getFeeData(); - return new ethers.FeeData( - null, - ((feedata.maxFeePerGas as bigint) * BigInt(parameters.multiplierGas)) / 1000n, - ((feedata.maxPriorityFeePerGas as bigint) * BigInt(parameters.multiplierGas)) / 1000n - ); - } - currentProvider.getFeeData = overrideFeeData; - } - } - } - - // Load deployer - let deployer; - if (parameters.deployerPvtKey) { - deployer = new ethers.Wallet(parameters.deployerPvtKey, currentProvider); - } else if (process.env.MNEMONIC) { - deployer = ethers.HDNodeWallet.fromMnemonic( - ethers.Mnemonic.fromPhrase(process.env.MNEMONIC), - "m/44'/60'/0'/0/0" - ).connect(currentProvider); - } else { - [deployer] = await ethers.getSigners(); - } - - - const timelockContractFactory = await ethers.getContractFactory("PolygonZkEVMTimelock", deployer); + const timelockContractFactory = await ethers.getContractFactory("PolygonZkEVMTimelock"); const timelockContract = (await timelockContractFactory.attach( parameters.timelockContractAddress )) as PolygonZkEVMTimelock; @@ -118,34 +72,6 @@ async function main() { parameters: parameters.newMinDelay } - if(parameters.sendSchedule) { - const txScheduled = await timelockContract.schedule( - operation.target, - operation.value, - operation.data, - operation.predecessor, - operation.salt, - timelockDelay, - ); - await txScheduled.wait(); - console.log("SEND SCHEDULE") - } - if (parameters.sendSchedule && parameters.sendExecute) { - await wait(timelockDelay); - } - if(parameters.sendExecute) { - const txExecute = await timelockContract.execute( - operation.target, - operation.value, - operation.data, - operation.predecessor, - operation.salt - ); - await txExecute.wait(); - console.log("SEND EXECUTE") - console.log("newMinDelay: ", await timelockContract.getMinDelay()) - } - await fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); } @@ -166,10 +92,6 @@ function genOperation(target: any, value: any, data: any, predecessor: any, salt }; } -function wait(seconds: number): Promise { - return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); -} - main().catch((e) => { console.error(e); process.exit(1); diff --git a/tools/changeDelayTimelock/change_delay_timelock.json.example b/tools/changeDelayTimelock/change_delay_timelock.json.example index cfb0d70b..0a4ec9d0 100644 --- a/tools/changeDelayTimelock/change_delay_timelock.json.example +++ b/tools/changeDelayTimelock/change_delay_timelock.json.example @@ -3,11 +3,5 @@ "newMinDelay": 1800, "timeLockDelay": "", "predecessor": "", - "timelockSalt": "", - "deployerPvtKey": "", - "maxFeePerGas": "", - "maxPriorityFeePerGas": "", - "multiplierGas": "", - "sendSchedule": false, - "sendExecute": false + "timelockSalt": "" } \ No newline at end of file From ff1ebfa9755a138fa46a32e2e6ed996a0834281e Mon Sep 17 00:00:00 2001 From: Laia Soler Date: Thu, 5 Dec 2024 15:33:23 +0100 Subject: [PATCH 3/3] add decodedScheduleData tool change delay timelock --- .../changeDelayTimelock.ts | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tools/changeDelayTimelock/changeDelayTimelock.ts b/tools/changeDelayTimelock/changeDelayTimelock.ts index 872d6782..3fb5f103 100644 --- a/tools/changeDelayTimelock/changeDelayTimelock.ts +++ b/tools/changeDelayTimelock/changeDelayTimelock.ts @@ -1,6 +1,6 @@ /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if */ /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ -import {expect} from "chai"; +import {utils} from "ffjavascript"; import path = require("path"); import fs = require("fs"); @@ -66,13 +66,36 @@ async function main() { outputJson.scheduleData = scheduleData; outputJson.executeData = executeData; - outputJson.minDelay = timelockDelay; - outputJson.functionData = { - function: 'updateDelay', - parameters: parameters.newMinDelay + + // Decode the scheduleData for better readability + const timelockTx = timelockContractFactory.interface.parseTransaction({data: scheduleData}); + const paramsArray = timelockTx?.fragment.inputs as any; + const objectDecoded = {} as any; + + for (let i = 0; i < paramsArray?.length; i++) { + const currentParam = paramsArray[i]; + objectDecoded[currentParam.name] = timelockTx?.args[i]; + + if (currentParam.name == "data") { + const decodedTimelockTx = timelockContractFactory.interface.parseTransaction({ + data: timelockTx?.args[i], + }); + const objectDecodedData = {} as any; + const paramsArrayData = decodedTimelockTx?.fragment.inputs as any; + + objectDecodedData.signature = decodedTimelockTx?.signature; + objectDecodedData.selector = decodedTimelockTx?.selector; + + for (let j = 0; j < paramsArrayData?.length; j++) { + const currentParam = paramsArrayData[j]; + objectDecodedData[currentParam.name] = decodedTimelockTx?.args[j]; + } + objectDecoded["decodedData"] = objectDecodedData; + } } + outputJson.decodedScheduleData = objectDecoded; - await fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); + await fs.writeFileSync(pathOutputJson, JSON.stringify(utils.stringifyBigInts(outputJson), null, 1)); } // OZ test functions