From 6b51fcf86bdd6b5284495c26824e4f027dec9e64 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 7 Feb 2025 16:42:00 +0100 Subject: [PATCH 01/17] deploy timelock in TS modern hardhat --- deploy/2_deploy-timelock.ts | 42 ++++++++++++++ scripts/timelock.js | 109 ------------------------------------ 2 files changed, 42 insertions(+), 109 deletions(-) create mode 100644 deploy/2_deploy-timelock.ts delete mode 100644 scripts/timelock.js diff --git a/deploy/2_deploy-timelock.ts b/deploy/2_deploy-timelock.ts new file mode 100644 index 000000000..325c9af79 --- /dev/null +++ b/deploy/2_deploy-timelock.ts @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +import hre, { ethers } from 'hardhat'; +import { TimelockController__factory } from '../typechain'; +import { FactoryDeployerHelper } from '../utils/FactoryDeployerHelper'; +const CONFIG = require('../config/config.json'); + +/** + * Deploy TimelockController contract + */ +module.exports = async function () { + console.log('Deploying TimelockController..'); + const chainId = (await ethers.provider.getNetwork()).chainId; + const [owner] = await hre.ethers.getSigners(); + const deploymentOptions = CONFIG.chains[chainId] || CONFIG.chains.default; + const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.constants.HashZero; + + // Initialize factory deployer + const factoryDeployer = new FactoryDeployerHelper(owner, salt); + + // Deploy TimelockController + const WEEK_IN_SECONDS = 86400 * 7; // 7 days + const PROPOSERS_AND_EXECUTORS = [ + '0x9ED07B5DB7dAD3C9a0baA3E320E68Ce779063249', + '0x36e19bc6374c9cea5eb86622cf04c6b144b5b59c', + '0x56fa2d29a54b5349cd5d88ffa584bffb2986a656', + '0x9a78ecd77595ea305c6e5a0daed3669b17801d09', + '0xb5ad0c32fc5fcb5e4cba4c81f523e6d47a82ecd7', + '0xb906dc99340d0f3162dbc5b2539b0ad075649bcf', + ]; + + await factoryDeployer.deployWithFactory(new TimelockController__factory(), [ + WEEK_IN_SECONDS, + PROPOSERS_AND_EXECUTORS, + PROPOSERS_AND_EXECUTORS, + owner.address, + ]); +}; + +// Add deployment tags +module.exports.tags = ['TimelockController']; diff --git a/scripts/timelock.js b/scripts/timelock.js deleted file mode 100644 index 2278de106..000000000 --- a/scripts/timelock.js +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: 2020 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -const { ethers } = require('ethers'); -const CONFIG = require('../config/config.json'); - -artifacts = { - require: (name) => { - try { - return require(`${process.cwd()}/build/contracts/${name}.json`); - } catch {} - try { - return require(`${process.cwd()}/node_modules/${name}.json`); - } catch {} - }, -}; - -const FACTORY = require('@iexec/solidity/deployment/factory.json'); -const TimelockController = artifacts.require('TimelockController'); - -const LIBRARIES = []; - -/***************************************************************************** - * Tools * - *****************************************************************************/ -class FactoryDeployer { - constructor(options) { - this._factory = new ethers.Contract(FACTORY.address, FACTORY.abi, options.wallet); - this._salt = options.salt || ethers.utils.randomBytes(32); - } - - async deploy(artefact, options = {}) { - console.log(`[factoryDeployer] ${artefact.contractName}`); - const libraryAddresses = await Promise.all( - LIBRARIES.filter( - ({ contractName }) => artefact.bytecode.search(contractName) != -1, - ).map(({ contractName, networks }) => ({ - pattern: new RegExp( - `__${contractName}${'_'.repeat(38 - contractName.length)}`, - 'g', - ), - address: networks[options.chainid].address, - })), - ); - - const constructorABI = artefact.abi.find((e) => e.type == 'constructor'); - const coreCode = libraryAddresses.reduce( - (code, { pattern, address }) => code.replace(pattern, address.slice(2).toLowerCase()), - artefact.bytecode, - ); - const argsCode = constructorABI - ? ethers.utils.defaultAbiCoder - .encode( - constructorABI.inputs.map((e) => e.type), - options.args || [], - ) - .slice(2) - : ''; - const code = coreCode + argsCode; - const salt = options.salt || this._salt || ethers.constants.HashZero; - const predicted = options.call - ? await this._factory.predictAddressWithCall(code, salt, options.call) - : await this._factory.predictAddress(code, salt); - - if ((await this._factory.provider.getCode(predicted)) == '0x') { - console.log(`[factory] Preparing to deploy ${artefact.contractName} ...`); - options.call - ? await this._factory.createContractAndCall(code, salt, options.call) - : await this._factory.createContract(code, salt); - console.log(`[factory] ${artefact.contractName} successfully deployed at ${predicted}`); - } else { - console.log(`[factory] ${artefact.contractName} already deployed at ${predicted}`); - } - artefact.networks[await this._factory.signer.getChainId()] = { address: predicted }; - } -} - -(async () => { - const provider = new ethers.getDefaultProvider(process.env.NODE); - const wallet = new ethers.Wallet(process.env.MNEMONIC, provider); - const chainid = await wallet.getChainId(); - const deploymentOptions = CONFIG.chains[chainid] || CONFIG.chains.default; - - // Deployer - const deployer = new FactoryDeployer({ - wallet, - chainid, - salt: deploymentOptions.v5.salt, - }); - await deployer.deploy(TimelockController, { - args: [ - 86400 * 7, // 7 days - [ - '0x9ED07B5DB7dAD3C9a0baA3E320E68Ce779063249', - '0x36e19bc6374c9cea5eb86622cf04c6b144b5b59c', - '0x56fa2d29a54b5349cd5d88ffa584bffb2986a656', - '0x9a78ecd77595ea305c6e5a0daed3669b17801d09', - '0xb5ad0c32fc5fcb5e4cba4c81f523e6d47a82ecd7', - '0xb906dc99340d0f3162dbc5b2539b0ad075649bcf', - ], - [ - '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer - ], - [ - '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer - ], - ], - }); -})().catch(console.error); From 553f5d5c2293865d0f7e114677dbe1dddc335eb0 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 10:18:49 +0100 Subject: [PATCH 02/17] set timelock in script folder --- deploy/2_deploy-timelock.ts => scripts/timelock.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename deploy/2_deploy-timelock.ts => scripts/timelock.ts (100%) diff --git a/deploy/2_deploy-timelock.ts b/scripts/timelock.ts similarity index 100% rename from deploy/2_deploy-timelock.ts rename to scripts/timelock.ts From b9810ae8a383f57195116b02c756cd3c0664e298 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 10:57:52 +0100 Subject: [PATCH 03/17] migrate test storage to TS --- scripts/test-storage.js | 58 ----------------------- scripts/test-storage.ts | 100 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 58 deletions(-) delete mode 100644 scripts/test-storage.js create mode 100644 scripts/test-storage.ts diff --git a/scripts/test-storage.js b/scripts/test-storage.js deleted file mode 100644 index f9df5d706..000000000 --- a/scripts/test-storage.js +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2023 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -const fs = require('fs'); -const path = require('path'); -const semver = require('semver'); -const { findAll } = require('solidity-ast/utils'); -const { astDereferencer } = require('solidity-ast/utils'); -const { solcInputOutputDecoder } = require('@openzeppelin/upgrades-core/dist/src-decoder'); -const { extractStorageLayout } = require('@openzeppelin/upgrades-core/dist/storage/extract'); -const { getStorageUpgradeReport } = require('@openzeppelin/upgrades-core/dist/storage'); - -const layouts = {}; - -const build = 'artifacts/build-info'; -for (const artifact of fs.readdirSync(build)) { - const { solcVersion, input, output } = JSON.parse(fs.readFileSync(path.join(build, artifact))); - const decoder = solcInputOutputDecoder(input, output); - const deref = astDereferencer(output); - - for (const src in output.contracts) { - // Skip if no AST - if (!output.sources[src].ast) continue; - for (const contractDef of findAll('ContractDefinition', output.sources[src].ast)) { - // Skip libraries and interfaces that don't have storage anyway - if (['library', 'interface'].includes(contractDef.contractKind)) continue; - // Store storage layout for this version of this contract - layouts[contractDef.name] ??= {} - layouts[contractDef.name][solcVersion] = extractStorageLayout( - contractDef, - decoder, - deref, - output.contracts[src][contractDef.name].storageLayout, - ); - } - } -} - -for (const [ name, versions ] of Object.entries(layouts)) { - const keys = Object.keys(versions).sort(semver.compare); - switch (keys.length) { - case 0: // should never happen - case 1: // contract only available in one version - continue; - default: - console.log(`[${name}]`); - keys.slice(0,-1).forEach((v, i) => { - const report = getStorageUpgradeReport(versions[v], versions[keys[i+1]], {}); - if (report.ok) { - console.log(`- ${v} → ${keys[i+1]}: storage layout is compatible`); - } else { - console.log(report.explain()); - process.exitCode = 1; - } - }); - break; - } -} diff --git a/scripts/test-storage.ts b/scripts/test-storage.ts new file mode 100644 index 000000000..92bb112d2 --- /dev/null +++ b/scripts/test-storage.ts @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +import { solcInputOutputDecoder } from '@openzeppelin/upgrades-core/dist/src-decoder'; +import { getStorageUpgradeReport } from '@openzeppelin/upgrades-core/dist/storage'; +import { extractStorageLayout } from '@openzeppelin/upgrades-core/dist/storage/extract'; +import fs from 'fs'; +import path from 'path'; +import semver from 'semver'; +import { astDereferencer, findAll } from 'solidity-ast/utils'; + +interface ContractLayouts { + [contractName: string]: { + [version: string]: any; + }; +} + +/** + * Checks storage layout compatibility between different versions of contracts + * @returns true if all storage layouts are compatible, false otherwise + */ +export function checkStorageLayoutCompatibility(): boolean { + const layouts: ContractLayouts = {}; + const buildDir = 'artifacts/build-info'; + + // Read and process all build artifacts + for (const artifact of fs.readdirSync(buildDir)) { + const buildInfo = JSON.parse(fs.readFileSync(path.join(buildDir, artifact), 'utf8')); + const { solcVersion, input, output } = buildInfo; + const decoder = solcInputOutputDecoder(input, output); + const deref = astDereferencer(output); + + // Process each contract in the build output + for (const src in output.contracts) { + // Skip if no AST + if (!output.sources[src].ast) continue; + + // Process each contract definition + for (const contractDef of findAll('ContractDefinition', output.sources[src].ast)) { + // Skip libraries and interfaces that don't have storage + if (['library', 'interface'].includes(contractDef.contractKind)) continue; + + // Initialize storage layout for this contract if not exists + layouts[contractDef.name] = layouts[contractDef.name] || {}; + + // Store storage layout for this version + layouts[contractDef.name][solcVersion] = extractStorageLayout( + contractDef, + decoder, + deref, + output.contracts[src][contractDef.name].storageLayout, + ); + } + } + } + + let hasIncompatibleLayouts = false; + + // Check compatibility between versions + for (const [name, versions] of Object.entries(layouts)) { + const keys = Object.keys(versions).sort(semver.compare); + switch (keys.length) { + case 0: // should never happen + case 1: // contract only available in one version + continue; + default: + console.log(`[${name}]`); + keys.slice(0, -1).forEach((v, i) => { + const validationOptions = { + unsafeAllowRenames: true, + unsafeSkipStorageCheck: true, + unsafeAllowCustomTypes: true, + unsafeAllowLinkedLibraries: true, + unsafeAllow: [], + kind: 'transparent' as const, + }; + const report = getStorageUpgradeReport( + versions[v], + versions[keys[i + 1]], + validationOptions, + ); + if (report.ok) { + console.log(`- ${v} → ${keys[i + 1]}: storage layout is compatible`); + } else { + console.log(report.explain()); + hasIncompatibleLayouts = true; + } + }); + break; + } + } + + return !hasIncompatibleLayouts; +} + +// Run the check if this file is being run directly +if (require.main === module) { + const success = checkStorageLayoutCompatibility(); + process.exit(success ? 0 : 1); +} From 6a1997acfe83dfb61b818eb73b6c5ed52c886b79 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 10:58:02 +0100 Subject: [PATCH 04/17] update year --- scripts/timelock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/timelock.ts b/scripts/timelock.ts index 325c9af79..77ffe45ae 100644 --- a/scripts/timelock.ts +++ b/scripts/timelock.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH +// SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 import hre, { ethers } from 'hardhat'; From a91d693c8921713272359ad690d89ef38edd9aa4 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 12:33:53 +0100 Subject: [PATCH 05/17] update test-storage script to TypeScript --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60b13afb8..d7a093e65 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "prepare": "husky", "build": "npx hardhat compile", "deploy": "npx hardhat deploy", - "test-storage-layout": "npx hardhat run scripts/test-storage.js", + "test-storage-layout": "npx hardhat run scripts/test-storage.ts", "test": "REPORT_GAS=true npx hardhat test", "autotest": "./test.sh", "coverage": "npx hardhat coverage", From 46ef4a1f4f963d61bb4cb32a75ff6813d94c9718 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 13:55:07 +0100 Subject: [PATCH 06/17] migrate common-test-snapshot to TS --- scripts/common-test-snapshot.js | 13 ------------- scripts/common-test-snapshot.ts | 13 +++++++++++++ scripts/hardhat-fixture-deployer.ts | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 scripts/common-test-snapshot.js create mode 100644 scripts/common-test-snapshot.ts diff --git a/scripts/common-test-snapshot.js b/scripts/common-test-snapshot.js deleted file mode 100644 index 9f8becf1c..000000000 --- a/scripts/common-test-snapshot.js +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -async function resetNetworkToInitialState() { - console.log( - 'Reset network to a fresh state to ensure same initial snapshot state between tests', - ); - await hre.network.provider.send('hardhat_reset'); -} - -module.exports = { - resetNetworkToInitialState, -}; diff --git a/scripts/common-test-snapshot.ts b/scripts/common-test-snapshot.ts new file mode 100644 index 000000000..d3c7eb048 --- /dev/null +++ b/scripts/common-test-snapshot.ts @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +declare const hre: HardhatRuntimeEnvironment; + +export async function resetNetworkToInitialState(): Promise { + console.log( + 'Reset network to a fresh state to ensure same initial snapshot state between tests', + ); + await hre.network.provider.send('hardhat_reset'); +} diff --git a/scripts/hardhat-fixture-deployer.ts b/scripts/hardhat-fixture-deployer.ts index ce7b7c7bc..ab0c45f5e 100644 --- a/scripts/hardhat-fixture-deployer.ts +++ b/scripts/hardhat-fixture-deployer.ts @@ -6,7 +6,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { deployments, ethers } from 'hardhat'; import { IexecInterfaceNative__factory } from '../typechain'; import { getIexecAccounts } from '../utils/poco-tools'; -const { resetNetworkToInitialState } = require('./common-test-snapshot'); +import { resetNetworkToInitialState } from './common-test-snapshot'; const deploy = require('../deploy/0_deploy'); const deployEns = require('../deploy/1_deploy-ens'); From fd1f44b4dba1ec85b6fa29f5242aefba9334e044 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 14:07:38 +0100 Subject: [PATCH 07/17] update getFunctionSignatures to TS --- migrations/utils/getFunctionSignatures.d.ts | 4 --- migrations/utils/getFunctionSignatures.js | 23 --------------- migrations/utils/getFunctionSignatures.ts | 32 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 27 deletions(-) delete mode 100644 migrations/utils/getFunctionSignatures.d.ts delete mode 100644 migrations/utils/getFunctionSignatures.js create mode 100644 migrations/utils/getFunctionSignatures.ts diff --git a/migrations/utils/getFunctionSignatures.d.ts b/migrations/utils/getFunctionSignatures.d.ts deleted file mode 100644 index 6def47725..000000000 --- a/migrations/utils/getFunctionSignatures.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -export function getFunctionSignatures(abi: any[]): string; diff --git a/migrations/utils/getFunctionSignatures.js b/migrations/utils/getFunctionSignatures.js deleted file mode 100644 index ba1404f57..000000000 --- a/migrations/utils/getFunctionSignatures.js +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2023-2024 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -/***************************************************************************** - * Tools * - *****************************************************************************/ -function getSerializedObject(entry) { - return entry.type == 'tuple' - ? `(${entry.components.map(getSerializedObject).join(',')})` - : entry.type; -} -function getFunctionSignatures(abi) { - return [ - ...abi.filter((entry) => entry.type == 'receive').map((entry) => 'receive;'), - ...abi.filter((entry) => entry.type == 'fallback').map((entry) => 'fallback;'), - ...abi - .filter((entry) => entry.type == 'function') - .map((entry) => `${entry.name}(${entry.inputs.map(getSerializedObject).join(',')});`), - ] - .filter(Boolean) - .join(''); -} -exports.getFunctionSignatures = getFunctionSignatures; diff --git a/migrations/utils/getFunctionSignatures.ts b/migrations/utils/getFunctionSignatures.ts new file mode 100644 index 000000000..230560645 --- /dev/null +++ b/migrations/utils/getFunctionSignatures.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023-2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +/***************************************************************************** + * Tools * + *****************************************************************************/ + +interface AbiParameter { + type: string; + components?: AbiParameter[]; +} + +function getSerializedObject(entry: AbiParameter): string { + return entry.type === 'tuple' + ? `(${entry.components?.map(getSerializedObject).join(',') ?? ''})` + : entry.type; +} + +export function getFunctionSignatures(abi: any[]): string { + return [ + ...abi.filter((entry) => entry.type === 'receive').map(() => 'receive;'), + ...abi.filter((entry) => entry.type === 'fallback').map(() => 'fallback;'), + ...abi + .filter((entry) => entry.type === 'function') + .map( + (entry) => + `${entry.name}(${entry.inputs?.map(getSerializedObject).join(',') ?? ''});`, + ), + ] + .filter(Boolean) + .join(''); +} From 134871ecb71bfc463e6039166e73672505dbd224 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 14:23:58 +0100 Subject: [PATCH 08/17] use hre from hardhat --- scripts/common-test-snapshot.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/common-test-snapshot.ts b/scripts/common-test-snapshot.ts index d3c7eb048..bf9c2695f 100644 --- a/scripts/common-test-snapshot.ts +++ b/scripts/common-test-snapshot.ts @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -declare const hre: HardhatRuntimeEnvironment; +import hre from 'hardhat'; export async function resetNetworkToInitialState(): Promise { console.log( From c4de97c373c780c1d1f9af8c97cfaa1f63b4e7db Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 10 Feb 2025 14:43:25 +0100 Subject: [PATCH 09/17] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2ced08f..ad92f2649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ - [x] `IexecPoco2Delegate.sol` ### Features +- Migrate scripts to TypeScript: (#184) + - getFunctionSignatures.js + - common-test-snapshot.js + - test-storage.js + - timelock.js - Purge Truffle leftovers (#180, #181, #182) - Sunset Jenkins pipeline (#178) - Re-use variable in `IexecPoco2Delegate` in `contribute(...)` function. (#168) From a59cd29542e3d814a73e4e72b835af0116015aa0 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 09:51:06 +0100 Subject: [PATCH 10/17] update timilock constructor arg --- scripts/timelock.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/timelock.ts b/scripts/timelock.ts index 77ffe45ae..f060fc808 100644 --- a/scripts/timelock.ts +++ b/scripts/timelock.ts @@ -30,12 +30,26 @@ module.exports = async function () { '0xb906dc99340d0f3162dbc5b2539b0ad075649bcf', ]; - await factoryDeployer.deployWithFactory(new TimelockController__factory(), [ - WEEK_IN_SECONDS, - PROPOSERS_AND_EXECUTORS, - PROPOSERS_AND_EXECUTORS, - owner.address, - ]); + const ADMINISTRATORS = PROPOSERS_AND_EXECUTORS; + const PROPOSERS = [ + '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer + ]; + const EXECUTORS = [ + '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer + ]; + + const constructorArgs = [WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS]; + + console.log('Constructor Arguments:', { + minDelay: WEEK_IN_SECONDS, + administrators: ADMINISTRATORS, + proposers: PROPOSERS, + executors: EXECUTORS, + }); + + const timelockFactory = new TimelockController__factory(owner); + + await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs); }; // Add deployment tags From 8a19032b665a1be87791f5ce71f997428163d28c Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 09:54:08 +0100 Subject: [PATCH 11/17] refactor: update storage layout compatibility check with validation options --- scripts/test-storage.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scripts/test-storage.ts b/scripts/test-storage.ts index 92bb112d2..9bbffe965 100644 --- a/scripts/test-storage.ts +++ b/scripts/test-storage.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 +import { ValidationOptions } from '@openzeppelin/upgrades-core'; import { solcInputOutputDecoder } from '@openzeppelin/upgrades-core/dist/src-decoder'; import { getStorageUpgradeReport } from '@openzeppelin/upgrades-core/dist/storage'; import { extractStorageLayout } from '@openzeppelin/upgrades-core/dist/storage/extract'; @@ -66,18 +67,10 @@ export function checkStorageLayoutCompatibility(): boolean { default: console.log(`[${name}]`); keys.slice(0, -1).forEach((v, i) => { - const validationOptions = { - unsafeAllowRenames: true, - unsafeSkipStorageCheck: true, - unsafeAllowCustomTypes: true, - unsafeAllowLinkedLibraries: true, - unsafeAllow: [], - kind: 'transparent' as const, - }; const report = getStorageUpgradeReport( versions[v], versions[keys[i + 1]], - validationOptions, + {} as Required, ); if (report.ok) { console.log(`- ${v} → ${keys[i + 1]}: storage layout is compatible`); From 3f13b7303b8d31a817a0138725fe10ba6984054b Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 12:21:47 +0100 Subject: [PATCH 12/17] removed tools banner --- migrations/utils/getFunctionSignatures.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/migrations/utils/getFunctionSignatures.ts b/migrations/utils/getFunctionSignatures.ts index 230560645..dac6706b2 100644 --- a/migrations/utils/getFunctionSignatures.ts +++ b/migrations/utils/getFunctionSignatures.ts @@ -1,10 +1,6 @@ // SPDX-FileCopyrightText: 2023-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 -/***************************************************************************** - * Tools * - *****************************************************************************/ - interface AbiParameter { type: string; components?: AbiParameter[]; From fc6875cf68c62145e9952814170d8692c4010945 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 12:22:14 +0100 Subject: [PATCH 13/17] apply suggestion hasIncompatibleLayouts => hasCompatibleLayouts --- scripts/test-storage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/test-storage.ts b/scripts/test-storage.ts index 9bbffe965..026767479 100644 --- a/scripts/test-storage.ts +++ b/scripts/test-storage.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH +// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 import { ValidationOptions } from '@openzeppelin/upgrades-core'; @@ -55,7 +55,7 @@ export function checkStorageLayoutCompatibility(): boolean { } } - let hasIncompatibleLayouts = false; + let hasCompatibleLayouts = true; // Check compatibility between versions for (const [name, versions] of Object.entries(layouts)) { @@ -76,14 +76,14 @@ export function checkStorageLayoutCompatibility(): boolean { console.log(`- ${v} → ${keys[i + 1]}: storage layout is compatible`); } else { console.log(report.explain()); - hasIncompatibleLayouts = true; + hasCompatibleLayouts = false; } }); break; } } - return !hasIncompatibleLayouts; + return hasCompatibleLayouts; } // Run the check if this file is being run directly From ad6be3512ee4c260cf967f143d084e068995ad0e Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 12:24:00 +0100 Subject: [PATCH 14/17] apply suggestion and rename file --- scripts/{timelock.ts => deploy-timelock.ts} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename scripts/{timelock.ts => deploy-timelock.ts} (81%) diff --git a/scripts/timelock.ts b/scripts/deploy-timelock.ts similarity index 81% rename from scripts/timelock.ts rename to scripts/deploy-timelock.ts index f060fc808..079f8d0e6 100644 --- a/scripts/timelock.ts +++ b/scripts/deploy-timelock.ts @@ -1,13 +1,15 @@ // SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 +import { duration } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time'; import hre, { ethers } from 'hardhat'; import { TimelockController__factory } from '../typechain'; import { FactoryDeployerHelper } from '../utils/FactoryDeployerHelper'; const CONFIG = require('../config/config.json'); /** - * Deploy TimelockController contract + * Deploy TimelockController contract using the generic factory. + */ module.exports = async function () { console.log('Deploying TimelockController..'); @@ -20,8 +22,9 @@ module.exports = async function () { const factoryDeployer = new FactoryDeployerHelper(owner, salt); // Deploy TimelockController - const WEEK_IN_SECONDS = 86400 * 7; // 7 days - const PROPOSERS_AND_EXECUTORS = [ + const ONE_WEEK_IN_SECONDS = duration.days(7); + // Proposers and executors. + const ADMINISTRATORS = [ '0x9ED07B5DB7dAD3C9a0baA3E320E68Ce779063249', '0x36e19bc6374c9cea5eb86622cf04c6b144b5b59c', '0x56fa2d29a54b5349cd5d88ffa584bffb2986a656', @@ -29,8 +32,6 @@ module.exports = async function () { '0xb5ad0c32fc5fcb5e4cba4c81f523e6d47a82ecd7', '0xb906dc99340d0f3162dbc5b2539b0ad075649bcf', ]; - - const ADMINISTRATORS = PROPOSERS_AND_EXECUTORS; const PROPOSERS = [ '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer ]; @@ -38,10 +39,10 @@ module.exports = async function () { '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer ]; - const constructorArgs = [WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS]; + const constructorArgs = [ONE_WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS]; console.log('Constructor Arguments:', { - minDelay: WEEK_IN_SECONDS, + minDelay: ONE_WEEK_IN_SECONDS, administrators: ADMINISTRATORS, proposers: PROPOSERS, executors: EXECUTORS, From e54402d383aa7562fdf5ea9e2befb709de97b195 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 12:52:27 +0100 Subject: [PATCH 15/17] back to keeping tracks of incompatibilities --- scripts/test-storage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/test-storage.ts b/scripts/test-storage.ts index 026767479..bc0943e8f 100644 --- a/scripts/test-storage.ts +++ b/scripts/test-storage.ts @@ -55,7 +55,7 @@ export function checkStorageLayoutCompatibility(): boolean { } } - let hasCompatibleLayouts = true; + let hasIncompatibleLayouts = false; // Check compatibility between versions for (const [name, versions] of Object.entries(layouts)) { @@ -76,14 +76,14 @@ export function checkStorageLayoutCompatibility(): boolean { console.log(`- ${v} → ${keys[i + 1]}: storage layout is compatible`); } else { console.log(report.explain()); - hasCompatibleLayouts = false; + hasIncompatibleLayouts = true; } }); break; } } - return hasCompatibleLayouts; + return !hasIncompatibleLayouts; } // Run the check if this file is being run directly From a2003d1a66c7d9dc22e2a0c3896a13bc46556ade Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 13:05:57 +0100 Subject: [PATCH 16/17] make this file executable and use export --- scripts/deploy-timelock.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index 079f8d0e6..8e983ac69 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -8,10 +8,10 @@ import { FactoryDeployerHelper } from '../utils/FactoryDeployerHelper'; const CONFIG = require('../config/config.json'); /** - * Deploy TimelockController contract using the generic factory. - + * Deploy TimelockController contract using the generic factory. */ -module.exports = async function () { + +export const deploy = async () => { console.log('Deploying TimelockController..'); const chainId = (await ethers.provider.getNetwork()).chainId; const [owner] = await hre.ethers.getSigners(); @@ -23,7 +23,6 @@ module.exports = async function () { // Deploy TimelockController const ONE_WEEK_IN_SECONDS = duration.days(7); - // Proposers and executors. const ADMINISTRATORS = [ '0x9ED07B5DB7dAD3C9a0baA3E320E68Ce779063249', '0x36e19bc6374c9cea5eb86622cf04c6b144b5b59c', @@ -38,20 +37,14 @@ module.exports = async function () { const EXECUTORS = [ '0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer ]; - const constructorArgs = [ONE_WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS]; - - console.log('Constructor Arguments:', { - minDelay: ONE_WEEK_IN_SECONDS, - administrators: ADMINISTRATORS, - proposers: PROPOSERS, - executors: EXECUTORS, - }); - const timelockFactory = new TimelockController__factory(owner); - await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs); }; -// Add deployment tags -module.exports.tags = ['TimelockController']; +if (require.main === module) { + deploy().catch((error) => { + console.error(error); + process.exit(1); + }); +} From f1ee3eac267e08050e16625960f8d77fa2167f06 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 11 Feb 2025 13:06:33 +0100 Subject: [PATCH 17/17] update github CI to test timelock deployment script --- .github/workflows/main.yml | 2 ++ package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1f2e7ad74..2b0fb1cab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,6 +36,8 @@ jobs: # Basic deployment to make sure everything is ok. # Could be removed in the future if not relevant. run: npm run deploy + - name: Test Timelock Deployment + run: npm run deploy:timelock - name: Run coverage run: npm run coverage - name: Upload coverage reports to Codecov diff --git a/package.json b/package.json index d7a093e65..c18355529 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "prepare": "husky", "build": "npx hardhat compile", "deploy": "npx hardhat deploy", + "deploy:timelock": "hardhat run scripts/deploy-timelock.ts", "test-storage-layout": "npx hardhat run scripts/test-storage.ts", "test": "REPORT_GAS=true npx hardhat test", "autotest": "./test.sh",