Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- [x] `IexecPoco2Delegate.sol`

### Features
- Migrate scripts to TypeScript: (#184)
- `getFunctionSignatures.js`, `common-test-snapshot.js`, `test-storage.js`, `timelock.js`
- Migrated utility files to TypeScript : (#183)
- `FactoryDeployer.js`, `constants.js`, `odb-tools.js`
- Removed deprecated `scripts/ens/sidechain.js`
Expand Down
4 changes: 0 additions & 4 deletions migrations/utils/getFunctionSignatures.d.ts

This file was deleted.

23 changes: 0 additions & 23 deletions migrations/utils/getFunctionSignatures.js

This file was deleted.

28 changes: 28 additions & 0 deletions migrations/utils/getFunctionSignatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2023-2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

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('');
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"prepare": "husky",
"build": "npx hardhat compile",
"deploy": "npx hardhat deploy",
"test-storage-layout": "npx hardhat run scripts/test-storage.js",
"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",
"coverage": "npx hardhat coverage",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

async function resetNetworkToInitialState() {
import hre from 'hardhat';

export async function resetNetworkToInitialState(): Promise<void> {
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,
};
50 changes: 50 additions & 0 deletions scripts/deploy-timelock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// 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 using the generic factory.
*/

export const deploy = async () => {
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 ONE_WEEK_IN_SECONDS = duration.days(7);
const ADMINISTRATORS = [
'0x9ED07B5DB7dAD3C9a0baA3E320E68Ce779063249',
'0x36e19bc6374c9cea5eb86622cf04c6b144b5b59c',
'0x56fa2d29a54b5349cd5d88ffa584bffb2986a656',
'0x9a78ecd77595ea305c6e5a0daed3669b17801d09',
'0xb5ad0c32fc5fcb5e4cba4c81f523e6d47a82ecd7',
'0xb906dc99340d0f3162dbc5b2539b0ad075649bcf',
];
const PROPOSERS = [
'0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer
];
const EXECUTORS = [
'0x0B3a38b0A47aB0c5E8b208A703de366751Df5916', // v5 deployer
];
const constructorArgs = [ONE_WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS];
const timelockFactory = new TimelockController__factory(owner);
await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs);
const timelockAddress = await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs);
await deployments.save('TimelockController', {
abi: TimelockController__factory.abi as unknown as ABI,
address: timelockAddress,
bytecode: TimelockController__factory.bytecode,
args: constructorArgs,
});
console.log('Timelock:', timelockAddress);

};

if (require.main === module) {
deploy().catch((error) => {
console.error(error);
process.exit(1);
});
}
2 changes: 1 addition & 1 deletion scripts/hardhat-fixture-deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
58 changes: 0 additions & 58 deletions scripts/test-storage.js

This file was deleted.

93 changes: 93 additions & 0 deletions scripts/test-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// 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';
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 report = getStorageUpgradeReport(
versions[v],
versions[keys[i + 1]],
{} as Required<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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking for that thanks

if (require.main === module) {
const success = checkStorageLayoutCompatibility();
process.exit(success ? 0 : 1);
}
Loading