From 1e29b3c1b1651cd29d5b03ff1a3d68ad4ac67795 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 3 Apr 2025 10:04:52 +0200 Subject: [PATCH 01/39] add support to fuji fork --- hardhat.config.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 24e368b23..25f572bb1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -13,6 +13,7 @@ import chainConfig from './utils/config'; const isNativeChainType = chainConfig.isNativeChain(); const isLocalFork = process.env.LOCAL_FORK == 'true'; +const isFujiFork = process.env.FUJI_FORK == 'true'; const bellecourBlockscoutUrl = 'https://blockscout.bellecour.iex.ec'; /** @@ -28,6 +29,13 @@ const bellecourBaseConfig = { blockGasLimit: 6_700_000, }; +// Avalanche Fuji specific configuration +const fujiBaseConfig = { + hardfork: 'london', // Avalanche C-Chain supports EIP-1559 + gasPrice: 25_000_000_000, // 25 Gwei default + blockGasLimit: 8_000_000, +}; + const settings = { optimizer: { enabled: true, @@ -81,6 +89,18 @@ const config: HardhatUserConfig = { }, chainId: 134, }), + ...(isFujiFork && { + forking: { + url: + process.env.FUJI_RPC_URL || + 'https://lb.drpc.org/ogrpc?network=avalanche-fuji&dkey=AhEPbH3buE5zjj_dDMs3E2galVURtfsR7pNM5mzgoATf', + blockNumber: process.env.FUJI_BLOCK_NUMBER + ? parseInt(process.env.FUJI_BLOCK_NUMBER) + : undefined, + }, + chainId: 43113, + ...fujiBaseConfig, + }), }, 'external-hardhat': { ...defaultHardhatNetworkParams, @@ -93,6 +113,11 @@ const config: HardhatUserConfig = { accounts: 'remote', // Override defaults accounts for impersonation chainId: 134, }), + ...(isFujiFork && { + accounts: 'remote', // Override defaults accounts for impersonation + chainId: 43113, + ...fujiBaseConfig, + }), }, 'dev-native': { chainId: 65535, @@ -128,6 +153,21 @@ const config: HardhatUserConfig = { mnemonic: process.env.MNEMONIC || '', }, }, + // Add Fuji as a network + fuji: { + chainId: 43113, + url: process.env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', + accounts: { + mnemonic: process.env.MNEMONIC || '', + }, + ...fujiBaseConfig, + verify: { + etherscan: { + apiUrl: 'https://api-testnet.snowtrace.io', + apiKey: process.env.SNOWTRACE_API_KEY || '', + }, + }, + }, viviani: { chainId: 133, url: 'https://viviani.iex.ec', @@ -155,10 +195,19 @@ const config: HardhatUserConfig = { etherscan: { apiKey: { mainnet: process.env.ETHERSCAN_API_KEY || '', + fuji: process.env.SNOWTRACE_API_KEY || '', viviani: 'nothing', // a non-empty string is needed by the plugin. bellecour: 'nothing', // a non-empty string is needed by the plugin. }, customChains: [ + { + network: 'fuji', + chainId: 43113, + urls: { + apiURL: 'https://api-testnet.snowtrace.io/api', + browserURL: 'https://testnet.snowtrace.io/', + }, + }, { network: 'viviani', chainId: 133, @@ -194,7 +243,7 @@ const config: HardhatUserConfig = { // Used as mock or fake in UTs '@openzeppelin/contracts-v5/interfaces/IERC1271.sol', // Used in deployment - '@amxx/factory/contracts/v6/GenericFactory.sol', + 'createx/src/ICreateX.sol', ], keep: true, // Slither requires compiled dependencies }, From 5e833cdcf8e6f73358031dd50e3fda828d18ed36 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 3 Apr 2025 16:44:45 +0200 Subject: [PATCH 02/39] use createX on deploy script and test on arbitrum sepolia / fuji fork --- hardhat.config.ts | 50 ++++++++++++++++++++++++++++ package-lock.json | 10 ++++++ package.json | 6 +++- utils/FactoryDeployer.ts | 70 ++++++++++++++++------------------------ 4 files changed, 93 insertions(+), 43 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 25f572bb1..a4f60b048 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -14,6 +14,7 @@ import chainConfig from './utils/config'; const isNativeChainType = chainConfig.isNativeChain(); const isLocalFork = process.env.LOCAL_FORK == 'true'; const isFujiFork = process.env.FUJI_FORK == 'true'; +const isArbitrumSepoliaFork = process.env.ARBITRUM_SEPOLIA_FORK == 'true'; const bellecourBlockscoutUrl = 'https://blockscout.bellecour.iex.ec'; /** @@ -36,6 +37,13 @@ const fujiBaseConfig = { blockGasLimit: 8_000_000, }; +// Arbitrum Sepolia specific configuration +const arbitrumSepoliaBaseConfig = { + hardfork: 'london', + gasPrice: 100_000_000, // 0.1 Gwei default (Arbitrum has lower gas prices) + blockGasLimit: 30_000_000, // Arbitrum has higher block gas limits +}; + const settings = { optimizer: { enabled: true, @@ -101,6 +109,18 @@ const config: HardhatUserConfig = { chainId: 43113, ...fujiBaseConfig, }), + ...(isArbitrumSepoliaFork && { + forking: { + url: + process.env.ARBITRUM_SEPOLIA_RPC_URL || + 'https://sepolia-rollup.arbitrum.io/rpc', + blockNumber: process.env.ARBITRUM_SEPOLIA_BLOCK_NUMBER + ? parseInt(process.env.ARBITRUM_SEPOLIA_BLOCK_NUMBER) + : undefined, + }, + chainId: 421614, // Arbitrum Sepolia chain ID + ...arbitrumSepoliaBaseConfig, + }), }, 'external-hardhat': { ...defaultHardhatNetworkParams, @@ -118,6 +138,11 @@ const config: HardhatUserConfig = { chainId: 43113, ...fujiBaseConfig, }), + ...(isArbitrumSepoliaFork && { + accounts: 'remote', // Override defaults accounts for impersonation + chainId: 421614, + ...arbitrumSepoliaBaseConfig, + }), }, 'dev-native': { chainId: 65535, @@ -168,6 +193,21 @@ const config: HardhatUserConfig = { }, }, }, + // Add Arbitrum Sepolia as a network + 'arbitrum-sepolia': { + chainId: 421614, + url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', + accounts: { + mnemonic: process.env.MNEMONIC || '', + }, + ...arbitrumSepoliaBaseConfig, + verify: { + etherscan: { + apiUrl: 'https://api-sepolia.arbiscan.io', + apiKey: process.env.ARBISCAN_API_KEY || '', + }, + }, + }, viviani: { chainId: 133, url: 'https://viviani.iex.ec', @@ -196,6 +236,8 @@ const config: HardhatUserConfig = { apiKey: { mainnet: process.env.ETHERSCAN_API_KEY || '', fuji: process.env.SNOWTRACE_API_KEY || '', + avalancheFujiTestnet: process.env.SNOWTRACE_API_KEY || '', + arbitrumSepolia: process.env.ARBISCAN_API_KEY || '', viviani: 'nothing', // a non-empty string is needed by the plugin. bellecour: 'nothing', // a non-empty string is needed by the plugin. }, @@ -208,6 +250,14 @@ const config: HardhatUserConfig = { browserURL: 'https://testnet.snowtrace.io/', }, }, + { + network: 'arbitrumSepolia', + chainId: 421614, + urls: { + apiURL: 'https://api-sepolia.arbiscan.io/api', + browserURL: 'https://sepolia.arbiscan.io/', + }, + }, { network: 'viviani', chainId: 133, diff --git a/package-lock.json b/package-lock.json index e36e6726f..433bb6956 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@openzeppelin/contracts": "3.3.0", "@openzeppelin/contracts-v5": "npm:@openzeppelin/contracts@^5.0.2", "@uniswap/v2-periphery": "1.1.0-beta.0", + "createx": "github:pcaversaccio/createx#v1.0.0", "rlc-faucet-contract": "1.0.10" }, "devDependencies": { @@ -4141,6 +4142,11 @@ "dev": true, "peer": true }, + "node_modules/createx": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/pcaversaccio/createx.git#cbac803268835138f86a69bfe01fcf05a50e0447", + "license": "AGPL-3.0-only" + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -15394,6 +15400,10 @@ "dev": true, "peer": true }, + "createx": { + "version": "git+ssh://git@github.com/pcaversaccio/createx.git#cbac803268835138f86a69bfe01fcf05a50e0447", + "from": "createx@github:pcaversaccio/createx#v1.0.0" + }, "cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", diff --git a/package.json b/package.json index 4dd8acfa0..4ff878f48 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "deploy": "npx hardhat deploy", "deploy:timelock": "hardhat run scripts/deploy-timelock.ts", "check-storage-layout": "npx hardhat run scripts/check-storage.ts", + "clean:hardhat": "rm -rf deployments/hardhat", "test": "REPORT_GAS=true npx hardhat test", + "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npm run test", + "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npm run test", "test:native": "TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", "coverage": "npx hardhat coverage", "verify": "npx hardhat verify", @@ -50,6 +53,7 @@ "@openzeppelin/contracts": "3.3.0", "@openzeppelin/contracts-v5": "npm:@openzeppelin/contracts@^5.0.2", "@uniswap/v2-periphery": "1.1.0-beta.0", + "createx": "github:pcaversaccio/createx#v1.0.0", "rlc-faucet-contract": "1.0.10" }, "devDependencies": { @@ -77,4 +81,4 @@ "sol2uml": "After 2.5.19, see https://github.com/naddison36/sol2uml/issues/183", "solidity-coverage": "Fixing Hardhat's solidity-coverage: TypeError: provider.send is not a function" } -} \ No newline at end of file +} diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 4eda5e3c4..abc1ae8b3 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -1,19 +1,18 @@ // SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 -import factoryJson from '@amxx/factory/deployments/GenericFactory.json'; -import factoryShanghaiJson from '@amxx/factory/deployments/GenericFactory_shanghai.json'; import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { ContractFactory } from 'ethers'; -import hre, { deployments, ethers } from 'hardhat'; -import { GenericFactory, GenericFactory__factory } from '../typechain'; -import config from './config'; +import { deployments, ethers } from 'hardhat'; +import { ICreateX, ICreateX__factory } from '../typechain'; import { getBaseNameFromContractFactory } from './deploy-tools'; +const CREATE_X_ADDRESSES = '0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed'; + export class FactoryDeployer { owner: SignerWithAddress; salt: string; - genericFactory!: GenericFactory; + createX!: ICreateX; constructor(owner: SignerWithAddress, salt: string) { this.owner = owner; @@ -21,7 +20,7 @@ export class FactoryDeployer { } /** - * Deploy a contract through GenericFactory [and optionally trigger a call] + * Deploy a contract through CreateX [and optionally trigger a call] */ async deployWithFactory( contractFactory: ContractFactory, @@ -34,15 +33,27 @@ export class FactoryDeployer { if (!bytecode) { throw new Error('Failed to prepare bytecode'); } - let contractAddress = await (call - ? this.genericFactory.predictAddressWithCall(bytecode, this.salt, call) - : this.genericFactory.predictAddress(bytecode, this.salt)); + const initCodeHash = ethers.keccak256(bytecode); + const saltHash = ethers.keccak256(this.salt); + const contractAddress = await this.createX['computeCreate2Address(bytes32,bytes32)']( + saltHash, + initCodeHash, + ); + console.log(`Deploying at ${contractAddress}`); const previouslyDeployed = (await ethers.provider.getCode(contractAddress)) !== '0x'; if (!previouslyDeployed) { await ( call - ? this.genericFactory.createContractAndCall(bytecode, this.salt, call) - : this.genericFactory.createContract(bytecode, this.salt) + ? this.createX['deployCreate2AndInit(bytes32,bytes,bytes,(uint256,uint256))']( + this.salt, + bytecode, + call, + { + constructorAmount: 0, + initCallAmount: 0, + }, + ) + : this.createX['deployCreate2(bytes32,bytes)'](this.salt, bytecode) ).then((tx) => tx.wait()); } const contractName = getBaseNameFromContractFactory(contractFactory); @@ -62,40 +73,15 @@ export class FactoryDeployer { } private async init() { - if (this.genericFactory) { + if (this.createX) { // Already initialized. return; } - const factoryConfig: FactoryConfig = - !config.isNativeChain() && hre.network.name.includes('hardhat') - ? factoryShanghaiJson - : factoryJson; - this.genericFactory = GenericFactory__factory.connect(factoryConfig.address, this.owner); - if ((await ethers.provider.getCode(factoryConfig.address)) !== '0x') { - console.log(`→ Factory is available on this network`); + this.createX = ICreateX__factory.connect(CREATE_X_ADDRESSES, this.owner); + if ((await ethers.provider.getCode(CREATE_X_ADDRESSES)) !== '0x') { + console.log(`→ CreateX is available on this network at ${CREATE_X_ADDRESSES}`); return; } - try { - console.log(`→ Factory is not yet deployed on this network`); - await this.owner - .sendTransaction({ - to: factoryConfig.deployer, - value: factoryConfig.cost, - }) - .then((tx) => tx.wait()); - await ethers.provider.broadcastTransaction(factoryConfig.tx).then((tx) => tx.wait()); - console.log(`→ Factory successfully deployed`); - } catch (e) { - console.log(e); - throw new Error('→ Error deploying the factory'); - } + console.log(`→ Factory is not yet deployed on this network`); } } - -interface FactoryConfig { - address: string; - deployer: string; - cost: string; - tx: string; - abi: any[]; -} From 7f0345c416835875f6d6afa8b60db3e5126baa42 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 3 Apr 2025 16:45:06 +0200 Subject: [PATCH 03/39] update mocha timeout for real time fork --- hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index a4f60b048..cd26a89fa 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -317,7 +317,7 @@ const config: HardhatUserConfig = { 'Store.v8.sol', ], }, - mocha: { timeout: 50000 }, + mocha: { timeout: 300000 }, }; /** From 804664d1427758058c9f647a7ab3b25ff47d3d0f Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 7 Apr 2025 15:34:08 +0200 Subject: [PATCH 04/39] add abritrum sepolia and fuji networks --- config/config.json | 32 ++++++++++++++++++++++++++++++++ utils/config.ts | 3 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 6eb44e000..a2008168a 100644 --- a/config/config.json +++ b/config/config.json @@ -146,6 +146,38 @@ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, + "43113": { + "_comment": "Avalanche Fuji Testnet", + "asset": "Token", + "token": "0xb96484C8B0e27B08a86661a3c19A028f4d3e89ad", + "richman": "0xd88CF17D89533816E99C0427581aa8C72129037D", + "uniswap": false, + "v3": { + "Hub": null, + "AppRegistry": null, + "DatasetRegistry": null, + "WorkerpoolRegistry": null + }, + "v5": { + "usefactory": true, + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "421614": { + "_comment": "Arbitrum Sepolia Testnet", + "asset": "Token", + "uniswap": false, + "v3": { + "Hub": null, + "AppRegistry": null, + "DatasetRegistry": null, + "WorkerpoolRegistry": null + }, + "v5": { + "usefactory": true, + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "default": { "_comment": "asset should be Token or Native", "asset": "Token", diff --git a/utils/config.ts b/utils/config.ts index 7cdb9859a..ba377c1cc 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -1,5 +1,5 @@ -import { Category } from './poco-tools'; import json from '../config/config.json'; +import { Category } from './poco-tools'; const config = json as Config; @@ -39,6 +39,7 @@ type ChainConfig = { _comment: string; asset: string; token?: string | null; + richman?: string | null; uniswap?: boolean; etoken?: string; v3: { From f6dfe7b14875d42b27b067ab9b89257092d84101 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 7 Apr 2025 16:05:27 +0200 Subject: [PATCH 05/39] save RLC token address in deployment to get it in test --- deploy/0_deploy.ts | 38 ++++++++++++++----- .../IexecAccessors/IexecAccessors.test.ts | 2 +- utils/config.ts | 4 +- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 93a871267..6996d6e2d 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -3,7 +3,7 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { ZeroAddress, ZeroHash } from 'ethers'; -import { ethers } from 'hardhat'; +import { deployments, ethers } from 'hardhat'; import { AppRegistry__factory, DatasetRegistry__factory, @@ -37,8 +37,8 @@ import { WorkerpoolRegistry__factory, } from '../typechain'; import { Ownable__factory } from '../typechain/factories/@openzeppelin/contracts/access'; -import config from '../utils/config'; import { FactoryDeployer } from '../utils/FactoryDeployer'; +import config from '../utils/config'; import { linkContractToProxy } from '../utils/proxy-tools'; /** @@ -213,11 +213,31 @@ export default async function deploy() { } async function getOrDeployRlc(token: string, owner: SignerWithAddress) { - return token // token - ? token - : await new RLC__factory() - .connect(owner) - .deploy() - .then((contract) => contract.waitForDeployment()) - .then((contract) => contract.getAddress()); + const rlcFactory = new RLC__factory().connect(owner); + if (token) { + console.log(`Using existing RLC token at: ${token}`); + await deployments.save('RLC', { + abi: (rlcFactory as any).constructor.abi, + address: token, + bytecode: (await rlcFactory.getDeployTransaction()).data, + deployedBytecode: await ethers.provider.getCode(token), + }); + return token; + } else { + console.log('Deploying new RLC token...'); + const rlcContract = await rlcFactory.deploy(); + await rlcContract.waitForDeployment(); + const rlcAddress = await rlcContract.getAddress(); + + // Save the deployment to hardhat deployments + await deployments.save('RLC', { + abi: (rlcFactory as any).constructor.abi, + address: rlcAddress, + bytecode: (await rlcFactory.getDeployTransaction()).data, + deployedBytecode: await ethers.provider.getCode(rlcAddress), + }); + + console.log(`New RLC token deployed at: ${rlcAddress}`); + return rlcAddress; + } } diff --git a/test/byContract/IexecAccessors/IexecAccessors.test.ts b/test/byContract/IexecAccessors/IexecAccessors.test.ts index c97a208ab..c1843ac9b 100644 --- a/test/byContract/IexecAccessors/IexecAccessors.test.ts +++ b/test/byContract/IexecAccessors/IexecAccessors.test.ts @@ -124,7 +124,7 @@ describe('IexecAccessors', async () => { // TODO test the case where token() == 0x0 in native mode. it('token', async function () { - expect(await iexecPoco.token()).to.equal('0x5FbDB2315678afecb367f032d93F642f64180aa3'); + expect(await iexecPoco.token()).to.equal((await deployments.get('RLC')).address); }); it('viewDeal', async function () { diff --git a/utils/config.ts b/utils/config.ts index ba377c1cc..59cee183e 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -38,8 +38,8 @@ type Config = { type ChainConfig = { _comment: string; asset: string; - token?: string | null; - richman?: string | null; + token?: string | null; // The token deployed should be compatible with Approve and call + richman?: string | null; // The richman account is needed if the token is already deployed uniswap?: boolean; etoken?: string; v3: { From d86109ea5ae45bd603e2ff67ce1b7ec2cc0e1732 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 8 Apr 2025 11:34:56 +0200 Subject: [PATCH 06/39] not working version --- utils/FactoryDeployer.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index abc1ae8b3..34de64d58 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import factorySignedTxJson from 'createx/scripts/presigned-createx-deployment-transactions/signed_serialised_transaction_gaslimit_3000000_.json'; import { ContractFactory } from 'ethers'; import { deployments, ethers } from 'hardhat'; import { ICreateX, ICreateX__factory } from '../typechain'; @@ -82,6 +83,31 @@ export class FactoryDeployer { console.log(`→ CreateX is available on this network at ${CREATE_X_ADDRESSES}`); return; } - console.log(`→ Factory is not yet deployed on this network`); + // Use full in case of working with local hardhat network, or bellecour + try { + console.log(`→ Factory is not yet deployed on this network`); + const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); + console.log( + `→ Deploying factory at ${CREATE_X_ADDRESSES} with gasPrice: ${factorySignedTx.gasPrice} and gasLimit: ${factorySignedTx.gasLimit}`, + ); + const deployer = factorySignedTx.from; + console.log(`→ Deployer: ${deployer}`); + const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); + console.log(`→ Cost: ${cost}`); + const tx = factorySignedTxJson; + console.log(`→ Transaction: ${tx}`); + + await this.owner + .sendTransaction({ + to: deployer, + value: cost, + }) + .then((tx) => tx.wait()); + await ethers.provider.broadcastTransaction(tx).then((tx) => tx.wait()); + console.log(`→ Factory successfully deployed`); + } catch (e) { + console.log(e); + throw new Error('→ Error deploying the factory'); + } } } From c5372dfb69370f3a0f6d2066c6e4e93d90d566df Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 8 Apr 2025 15:15:57 +0200 Subject: [PATCH 07/39] deploy factory on local hardhat network --- utils/FactoryDeployer.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index abc1ae8b3..083346806 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import factorySignedTxJson from 'createx/scripts/presigned-createx-deployment-transactions/signed_serialised_transaction_gaslimit_3000000_.json'; import { ContractFactory } from 'ethers'; import { deployments, ethers } from 'hardhat'; import { ICreateX, ICreateX__factory } from '../typechain'; @@ -82,6 +83,27 @@ export class FactoryDeployer { console.log(`→ CreateX is available on this network at ${CREATE_X_ADDRESSES}`); return; } - console.log(`→ Factory is not yet deployed on this network`); + try { + console.log(`→ Factory is not yet deployed on this network`); + const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); + console.log( + `→ Deploying factory at ${CREATE_X_ADDRESSES} with gasPrice: ${factorySignedTx.gasPrice} and gasLimit: ${factorySignedTx.gasLimit}`, + ); + const deployer = factorySignedTx.from; + const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); + const tx = factorySignedTxJson; + + await this.owner + .sendTransaction({ + to: deployer, + value: cost, + }) + .then((tx) => tx.wait()); + await ethers.provider.broadcastTransaction(tx).then((tx) => tx.wait()); + console.log(`→ Factory successfully deployed`); + } catch (e) { + console.log(e); + throw new Error('→ Error deploying the factory'); + } } } From f7eba8ee0288562fb49121bf4ca34594bd429edb Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 8 Apr 2025 15:16:20 +0200 Subject: [PATCH 08/39] save RLC address on deploy --- deploy/0_deploy.ts | 38 ++++++++++++++----- .../IexecAccessors/IexecAccessors.test.ts | 2 +- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 93a871267..6996d6e2d 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -3,7 +3,7 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { ZeroAddress, ZeroHash } from 'ethers'; -import { ethers } from 'hardhat'; +import { deployments, ethers } from 'hardhat'; import { AppRegistry__factory, DatasetRegistry__factory, @@ -37,8 +37,8 @@ import { WorkerpoolRegistry__factory, } from '../typechain'; import { Ownable__factory } from '../typechain/factories/@openzeppelin/contracts/access'; -import config from '../utils/config'; import { FactoryDeployer } from '../utils/FactoryDeployer'; +import config from '../utils/config'; import { linkContractToProxy } from '../utils/proxy-tools'; /** @@ -213,11 +213,31 @@ export default async function deploy() { } async function getOrDeployRlc(token: string, owner: SignerWithAddress) { - return token // token - ? token - : await new RLC__factory() - .connect(owner) - .deploy() - .then((contract) => contract.waitForDeployment()) - .then((contract) => contract.getAddress()); + const rlcFactory = new RLC__factory().connect(owner); + if (token) { + console.log(`Using existing RLC token at: ${token}`); + await deployments.save('RLC', { + abi: (rlcFactory as any).constructor.abi, + address: token, + bytecode: (await rlcFactory.getDeployTransaction()).data, + deployedBytecode: await ethers.provider.getCode(token), + }); + return token; + } else { + console.log('Deploying new RLC token...'); + const rlcContract = await rlcFactory.deploy(); + await rlcContract.waitForDeployment(); + const rlcAddress = await rlcContract.getAddress(); + + // Save the deployment to hardhat deployments + await deployments.save('RLC', { + abi: (rlcFactory as any).constructor.abi, + address: rlcAddress, + bytecode: (await rlcFactory.getDeployTransaction()).data, + deployedBytecode: await ethers.provider.getCode(rlcAddress), + }); + + console.log(`New RLC token deployed at: ${rlcAddress}`); + return rlcAddress; + } } diff --git a/test/byContract/IexecAccessors/IexecAccessors.test.ts b/test/byContract/IexecAccessors/IexecAccessors.test.ts index c97a208ab..c1843ac9b 100644 --- a/test/byContract/IexecAccessors/IexecAccessors.test.ts +++ b/test/byContract/IexecAccessors/IexecAccessors.test.ts @@ -124,7 +124,7 @@ describe('IexecAccessors', async () => { // TODO test the case where token() == 0x0 in native mode. it('token', async function () { - expect(await iexecPoco.token()).to.equal('0x5FbDB2315678afecb367f032d93F642f64180aa3'); + expect(await iexecPoco.token()).to.equal((await deployments.get('RLC')).address); }); it('viewDeal', async function () { From 7246d45f06d2b5add904ed18b3680340663e45c2 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 8 Apr 2025 16:32:07 +0200 Subject: [PATCH 09/39] add support to fork tests chain --- config/config.json | 32 ++++++++++++ test/utils/hardhat-fixture-deployer.ts | 67 +++++++++++++++++++++++++- utils/config.ts | 5 +- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/config/config.json b/config/config.json index 6eb44e000..a2008168a 100644 --- a/config/config.json +++ b/config/config.json @@ -146,6 +146,38 @@ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, + "43113": { + "_comment": "Avalanche Fuji Testnet", + "asset": "Token", + "token": "0xb96484C8B0e27B08a86661a3c19A028f4d3e89ad", + "richman": "0xd88CF17D89533816E99C0427581aa8C72129037D", + "uniswap": false, + "v3": { + "Hub": null, + "AppRegistry": null, + "DatasetRegistry": null, + "WorkerpoolRegistry": null + }, + "v5": { + "usefactory": true, + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "421614": { + "_comment": "Arbitrum Sepolia Testnet", + "asset": "Token", + "uniswap": false, + "v3": { + "Hub": null, + "AppRegistry": null, + "DatasetRegistry": null, + "WorkerpoolRegistry": null + }, + "v5": { + "usefactory": true, + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "default": { "_comment": "asset should be Token or Native", "asset": "Token", diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index c3ab4657c..e38499e35 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -6,7 +6,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { deployments, ethers } from 'hardhat'; import deploy from '../../deploy/0_deploy'; import deployEns from '../../deploy/1_deploy-ens'; -import { IexecInterfaceNative__factory } from '../../typechain'; +import { IexecInterfaceNative__factory, IexecInterfaceToken__factory } from '../../typechain'; import config from '../../utils/config'; import { getIexecAccounts } from '../../utils/poco-tools'; @@ -16,6 +16,63 @@ async function deployAll() { return (await deployments.get('ERC1538Proxy')).address; } +async function setUpTokenFork() { + const chainId = (await ethers.provider.getNetwork()).chainId; + const chainConfig = config.getChainConfig(chainId); + const rlcTokenAddress = chainConfig.token; + const richmanAddress = chainConfig.richman; + const accounts = await getIexecAccounts(); + if (rlcTokenAddress && richmanAddress) { + const rlcToken = IexecInterfaceToken__factory.connect(rlcTokenAddress, ethers.provider); + const richmanSigner = await ethers.getImpersonatedSigner(richmanAddress); + await ethers.provider.send('hardhat_setBalance', [ + richmanAddress, + '0x1000000000000000000', // 1 ETH + ]); + const otherAccountInitAmount = 10000 * 10 ** 9; + const accountsArray = Object.values(accounts) as SignerWithAddress[]; + console.log(`Rich account ${richmanAddress} sending RLCs to other accounts..`); + + // Transfer RLC tokens to all accounts + for (let i = 0; i < accountsArray.length; i++) { + const account = accountsArray[i]; + await rlcToken + .connect(richmanSigner) + .transfer(account.address, otherAccountInitAmount) + .then((tx) => tx.wait()); + const balance = await rlcToken.balanceOf(account.address); + console.log(`Account #${i}: ${account.address} (${balance.toLocaleString()} nRLC)`); + } + } + + const proxyAddress = chainConfig.v5.ERC1538Proxy; + if (proxyAddress) { + console.log(`Using existing ERC1538Proxy at ${proxyAddress}`); + const iexecPoco = IexecInterfaceToken__factory.connect(proxyAddress, ethers.provider); + const timelockAddress = await iexecPoco.owner(); + const timelock = await ethers.getImpersonatedSigner(timelockAddress); + const newIexecAdminAddress = accounts.iexecAdmin.address; + console.log( + `Transferring Poco ownership from Timelock:${timelockAddress} to iexecAdmin:${newIexecAdminAddress}`, + ); + + await iexecPoco + .connect(timelock) + .transferOwnership(newIexecAdminAddress) + .then((tx) => tx.wait()); + + return proxyAddress; + } else { + console.log('No existing ERC1538Proxy found, deploying new contracts'); + // Deploy all contracts + await deploy(); + await deployEns(); + const newProxyAddress = (await deployments.get('ERC1538Proxy')).address; + console.log(`Deployed new ERC1538Proxy at ${newProxyAddress}`); + return newProxyAddress; + } +} + async function setUpLocalFork() { const chainId = (await ethers.provider.getNetwork()).chainId; const proxyAddress = config.getChainConfig(chainId).v5.ERC1538Proxy; @@ -58,5 +115,11 @@ async function setUpLocalFork() { * @returns proxy address. */ export const loadHardhatFixtureDeployment = async () => { - return await loadFixture(process.env.LOCAL_FORK != 'true' ? deployAll : setUpLocalFork); + if (process.env.LOCAL_FORK == 'true') { + return await loadFixture(setUpLocalFork); + } else if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { + return await loadFixture(setUpTokenFork); + } else { + return await loadFixture(deployAll); + } }; diff --git a/utils/config.ts b/utils/config.ts index 7cdb9859a..59cee183e 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -1,5 +1,5 @@ -import { Category } from './poco-tools'; import json from '../config/config.json'; +import { Category } from './poco-tools'; const config = json as Config; @@ -38,7 +38,8 @@ type Config = { type ChainConfig = { _comment: string; asset: string; - token?: string | null; + token?: string | null; // The token deployed should be compatible with Approve and call + richman?: string | null; // The richman account is needed if the token is already deployed uniswap?: boolean; etoken?: string; v3: { From ba423b0726a6ca994546fea7024e59353d7142f5 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 8 Apr 2025 16:50:00 +0200 Subject: [PATCH 10/39] add clean hardhat on test:native --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ff878f48..d5bbe8ab3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "REPORT_GAS=true npx hardhat test", "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npm run test", "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npm run test", - "test:native": "TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", + "test:native": "npm run clean:hardhat && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", "coverage": "npx hardhat coverage", "verify": "npx hardhat verify", "format": "npx prettier --write", From 029d9163a49b445528bbbda0508f2c0fcf4c9d1e Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 15:53:07 +0200 Subject: [PATCH 11/39] add setUpLocalFork --- test/utils/hardhat-fixture-deployer.ts | 67 +++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index c3ab4657c..86517c7a5 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -6,7 +6,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { deployments, ethers } from 'hardhat'; import deploy from '../../deploy/0_deploy'; import deployEns from '../../deploy/1_deploy-ens'; -import { IexecInterfaceNative__factory } from '../../typechain'; +import { IexecInterfaceNative__factory, IexecInterfaceToken__factory } from '../../typechain'; import config from '../../utils/config'; import { getIexecAccounts } from '../../utils/poco-tools'; @@ -54,9 +54,72 @@ async function setUpLocalFork() { return proxyAddress; } +async function setUpTokenFork() { + const chainId = (await ethers.provider.getNetwork()).chainId; + const chainConfig = config.getChainConfig(chainId); + const rlcTokenAddress = chainConfig.token; + const richmanAddress = chainConfig.richman; + const accounts = await getIexecAccounts(); + if (rlcTokenAddress && richmanAddress) { + const rlcToken = IexecInterfaceToken__factory.connect(rlcTokenAddress, ethers.provider); + const richmanSigner = await ethers.getImpersonatedSigner(richmanAddress); + await ethers.provider.send('hardhat_setBalance', [ + richmanAddress, + '0x1000000000000000000', // 1 ETH + ]); + const otherAccountInitAmount = 10000 * 10 ** 9; + const accountsArray = Object.values(accounts) as SignerWithAddress[]; + console.log(`Rich account ${richmanAddress} sending RLCs to other accounts..`); + + // Transfer RLC tokens to all accounts + for (let i = 0; i < accountsArray.length; i++) { + const account = accountsArray[i]; + await rlcToken + .connect(richmanSigner) + .transfer(account.address, otherAccountInitAmount) + .then((tx) => tx.wait()); + const balance = await rlcToken.balanceOf(account.address); + console.log(`Account #${i}: ${account.address} (${balance.toLocaleString()} nRLC)`); + } + } + + const proxyAddress = chainConfig.v5.ERC1538Proxy; + if (proxyAddress) { + console.log(`Using existing ERC1538Proxy at ${proxyAddress}`); + const iexecPoco = IexecInterfaceToken__factory.connect(proxyAddress, ethers.provider); + const timelockAddress = await iexecPoco.owner(); + const timelock = await ethers.getImpersonatedSigner(timelockAddress); + const newIexecAdminAddress = accounts.iexecAdmin.address; + console.log( + `Transferring Poco ownership from Timelock:${timelockAddress} to iexecAdmin:${newIexecAdminAddress}`, + ); + + await iexecPoco + .connect(timelock) + .transferOwnership(newIexecAdminAddress) + .then((tx) => tx.wait()); + + return proxyAddress; + } else { + console.log('No existing ERC1538Proxy found, deploying new contracts'); + // Deploy all contracts + await deploy(); + await deployEns(); + const newProxyAddress = (await deployments.get('ERC1538Proxy')).address; + console.log(`Deployed new ERC1538Proxy at ${newProxyAddress}`); + return newProxyAddress; + } +} + /** * @returns proxy address. */ export const loadHardhatFixtureDeployment = async () => { - return await loadFixture(process.env.LOCAL_FORK != 'true' ? deployAll : setUpLocalFork); + if (process.env.LOCAL_FORK == 'true') { + return await loadFixture(setUpLocalFork); + } else if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { + return await loadFixture(setUpTokenFork); + } else { + return await loadFixture(deployAll); + } }; From 1b56be81d27c66c54863c5bbd3e90c75ff1d56ea Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 15:53:25 +0200 Subject: [PATCH 12/39] add clean before test native --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ff878f48..d5bbe8ab3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "REPORT_GAS=true npx hardhat test", "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npm run test", "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npm run test", - "test:native": "TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", + "test:native": "npm run clean:hardhat && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", "coverage": "npx hardhat coverage", "verify": "npx hardhat verify", "format": "npx prettier --write", From 451b684f0d7ddbab4f2c092b3bde1d502d845937 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 15:54:22 +0200 Subject: [PATCH 13/39] add support to generic factory on bellecour --- config/config.json | 4 +++ deploy/0_deploy.ts | 4 ++- hardhat.config.ts | 1 + utils/FactoryDeployer.ts | 78 +++++++++++++++++++++++++++++++--------- utils/config.ts | 1 + 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/config/config.json b/config/config.json index a2008168a..376570cb1 100644 --- a/config/config.json +++ b/config/config.json @@ -143,6 +143,7 @@ "ERC1538Proxy": "0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f", "IexecLibOrders_v5": "0xE8b04c85C47fcEc0e9eE30D4034e2997f6519123", "usefactory": true, + "Factory": "0xfAC000a12dA42B871c0AaD5F25391aAe62958Db1", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -160,6 +161,7 @@ }, "v5": { "usefactory": true, + "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -175,6 +177,7 @@ }, "v5": { "usefactory": true, + "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -191,6 +194,7 @@ }, "v5": { "usefactory": true, + "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } } diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 6996d6e2d..6be13c0cb 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -57,7 +57,9 @@ export default async function deploy() { const [owner] = await ethers.getSigners(); const deploymentOptions = config.getChainConfigOrDefault(chainId); const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.ZeroHash; - const factoryDeployer = new FactoryDeployer(owner, salt); + const factoryAddress = + process.env.FACTORY_ADDRESS || deploymentOptions.v5.Factory || ethers.ZeroAddress; + const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy RLC const isTokenMode = !config.isNativeChain(deploymentOptions); let rlcInstanceAddress = isTokenMode diff --git a/hardhat.config.ts b/hardhat.config.ts index cd26a89fa..1052ea22b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -293,6 +293,7 @@ const config: HardhatUserConfig = { // Used as mock or fake in UTs '@openzeppelin/contracts-v5/interfaces/IERC1271.sol', // Used in deployment + '@amxx/factory/contracts/v6/GenericFactory.sol', 'createx/src/ICreateX.sol', ], keep: true, // Slither requires compiled dependencies diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 34de64d58..6e555485c 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -5,19 +5,20 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import factorySignedTxJson from 'createx/scripts/presigned-createx-deployment-transactions/signed_serialised_transaction_gaslimit_3000000_.json'; import { ContractFactory } from 'ethers'; import { deployments, ethers } from 'hardhat'; -import { ICreateX, ICreateX__factory } from '../typechain'; +import { GenericFactory, GenericFactory__factory, ICreateX, ICreateX__factory } from '../typechain'; import { getBaseNameFromContractFactory } from './deploy-tools'; -const CREATE_X_ADDRESSES = '0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed'; - export class FactoryDeployer { owner: SignerWithAddress; salt: string; + factoryAddress: string; createX!: ICreateX; + genericFactory!: GenericFactory; - constructor(owner: SignerWithAddress, salt: string) { + constructor(owner: SignerWithAddress, salt: string, factoryAddress: string) { this.owner = owner; this.salt = salt; + this.factoryAddress = factoryAddress; } /** @@ -28,7 +29,7 @@ export class FactoryDeployer { constructorArgs?: any[], call?: string, ) { - await this.init(); + await this.initCreateX(); let bytecode = (await contractFactory.getDeployTransaction(...(constructorArgs ?? []))) .data; if (!bytecode) { @@ -73,29 +74,74 @@ export class FactoryDeployer { return contractAddress; } - private async init() { + /** + * Deploy a contract through GenericFactory [and optionally trigger a call] + */ + async deployWithGeneric( + contractFactory: ContractFactory, + constructorArgs?: any[], + call?: string, + ) { + await this.initLegacy(); + let bytecode = (await contractFactory.getDeployTransaction(...(constructorArgs ?? []))) + .data; + if (!bytecode) { + throw new Error('Failed to prepare bytecode'); + } + let contractAddress = await (call + ? this.genericFactory.predictAddressWithCall(bytecode, this.salt, call) + : this.genericFactory.predictAddress(bytecode, this.salt)); + const previouslyDeployed = (await ethers.provider.getCode(contractAddress)) !== '0x'; + if (!previouslyDeployed) { + await ( + call + ? this.genericFactory.createContractAndCall(bytecode, this.salt, call) + : this.genericFactory.createContract(bytecode, this.salt) + ).then((tx) => tx.wait()); + } + const contractName = getBaseNameFromContractFactory(contractFactory); + console.log( + `${contractName}: ${contractAddress} ${ + previouslyDeployed ? ' (previously deployed)' : '' + }`, + ); + await deployments.save(contractName, { + // abi field is not used but is a required arg. Empty abi would be fine + abi: (contractFactory as any).constructor.abi, + address: contractAddress, + bytecode: bytecode, + args: constructorArgs, + }); + return contractAddress; + } + + private async initLegacy() { + if (this.genericFactory) { + return; + } + this.genericFactory = GenericFactory__factory.connect(this.factoryAddress, this.owner); + if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { + console.log(`→ Factory is available on this network`); + return; + } + } + + private async initCreateX() { if (this.createX) { - // Already initialized. return; } - this.createX = ICreateX__factory.connect(CREATE_X_ADDRESSES, this.owner); - if ((await ethers.provider.getCode(CREATE_X_ADDRESSES)) !== '0x') { - console.log(`→ CreateX is available on this network at ${CREATE_X_ADDRESSES}`); + this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); + if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { + console.log(`→ CreateX is available on this network at ${this.factoryAddress}`); return; } // Use full in case of working with local hardhat network, or bellecour try { console.log(`→ Factory is not yet deployed on this network`); const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); - console.log( - `→ Deploying factory at ${CREATE_X_ADDRESSES} with gasPrice: ${factorySignedTx.gasPrice} and gasLimit: ${factorySignedTx.gasLimit}`, - ); const deployer = factorySignedTx.from; - console.log(`→ Deployer: ${deployer}`); const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); - console.log(`→ Cost: ${cost}`); const tx = factorySignedTxJson; - console.log(`→ Transaction: ${tx}`); await this.owner .sendTransaction({ diff --git a/utils/config.ts b/utils/config.ts index 59cee183e..9fc5f7440 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -50,6 +50,7 @@ type ChainConfig = { }; v5: { usefactory: boolean; + Factory?: string; salt: string; AppRegistry?: string; DatasetRegistry?: string; From 8a4f8f8ef45adb22829afee51b91286741501190 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 16:09:32 +0200 Subject: [PATCH 14/39] update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36b93e02..1d0a05c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [x] `IexecPoco2Delegate.sol` ### Features +- Add CreateX factory for new chain X deployment (#215) - Housekeeping (#207, ) - Add Halborn "Poco v5.5 & Voucher v1.0" audit report (#205) - Refactor Factory deployer (#206) From 3b792bea3afed1921078f4cd64a280bb0387cb21 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 16:16:45 +0200 Subject: [PATCH 15/39] update fuji public rpc --- hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 1052ea22b..f612f51cc 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -101,7 +101,7 @@ const config: HardhatUserConfig = { forking: { url: process.env.FUJI_RPC_URL || - 'https://lb.drpc.org/ogrpc?network=avalanche-fuji&dkey=AhEPbH3buE5zjj_dDMs3E2galVURtfsR7pNM5mzgoATf', + 'https://avalanche-fuji-c-chain-rpc.publicnode.com', blockNumber: process.env.FUJI_BLOCK_NUMBER ? parseInt(process.env.FUJI_BLOCK_NUMBER) : undefined, From 83564528de78ac47a6ac21f8b362349da5966a99 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Wed, 9 Apr 2025 16:21:56 +0200 Subject: [PATCH 16/39] fixe timelock --- scripts/deploy-timelock.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index 9fad81e83..40908fdc2 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -15,10 +15,13 @@ export const deploy = async () => { console.log('Deploying TimelockController..'); const chainId = (await ethers.provider.getNetwork()).chainId; const [owner] = await ethers.getSigners(); - const salt = process.env.SALT || config.getChainConfigOrDefault(chainId).v5.salt; + const deploymentOptions = config.getChainConfigOrDefault(chainId); + const salt = process.env.SALT || deploymentOptions.v5.salt; // Initialize factory deployer - const factoryDeployer = new FactoryDeployer(owner, salt); + const factoryAddress = + process.env.FACTORY_ADDRESS || deploymentOptions.v5.Factory || ethers.ZeroAddress; + const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy TimelockController const ONE_WEEK_IN_SECONDS = duration.days(7); From a8ffb0c66f269dc8fb09abc615d7713172efff3f Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 15:35:43 +0200 Subject: [PATCH 17/39] add clean on test npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5bbe8ab3..7b562a681 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "deploy:timelock": "hardhat run scripts/deploy-timelock.ts", "check-storage-layout": "npx hardhat run scripts/check-storage.ts", "clean:hardhat": "rm -rf deployments/hardhat", - "test": "REPORT_GAS=true npx hardhat test", + "test": "npm run clean:hardhat && REPORT_GAS=true npx hardhat test", "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npm run test", "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npm run test", "test:native": "npm run clean:hardhat && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", From 385d8bbaefcc64a89ff272b5fe2cc7220722cb84 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 15:41:28 +0200 Subject: [PATCH 18/39] lowercase for factory addy --- config/config.json | 8 ++++---- deploy/0_deploy.ts | 2 +- scripts/deploy-timelock.ts | 2 +- utils/config.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config/config.json b/config/config.json index 376570cb1..7d0bddbb5 100644 --- a/config/config.json +++ b/config/config.json @@ -143,7 +143,7 @@ "ERC1538Proxy": "0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f", "IexecLibOrders_v5": "0xE8b04c85C47fcEc0e9eE30D4034e2997f6519123", "usefactory": true, - "Factory": "0xfAC000a12dA42B871c0AaD5F25391aAe62958Db1", + "factory": "0xfAC000a12dA42B871c0AaD5F25391aAe62958Db1", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -161,7 +161,7 @@ }, "v5": { "usefactory": true, - "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -177,7 +177,7 @@ }, "v5": { "usefactory": true, - "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -194,7 +194,7 @@ }, "v5": { "usefactory": true, - "Factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } } diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 6be13c0cb..d72bc664d 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -58,7 +58,7 @@ export default async function deploy() { const deploymentOptions = config.getChainConfigOrDefault(chainId); const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.ZeroHash; const factoryAddress = - process.env.FACTORY_ADDRESS || deploymentOptions.v5.Factory || ethers.ZeroAddress; + process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory || ethers.ZeroAddress; const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy RLC const isTokenMode = !config.isNativeChain(deploymentOptions); diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index 40908fdc2..f0521ee7a 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -20,7 +20,7 @@ export const deploy = async () => { // Initialize factory deployer const factoryAddress = - process.env.FACTORY_ADDRESS || deploymentOptions.v5.Factory || ethers.ZeroAddress; + process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory || ethers.ZeroAddress; const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy TimelockController diff --git a/utils/config.ts b/utils/config.ts index 9fc5f7440..40f557eb7 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -50,7 +50,7 @@ type ChainConfig = { }; v5: { usefactory: boolean; - Factory?: string; + factory?: string; salt: string; AppRegistry?: string; DatasetRegistry?: string; From 709b77c2ce91426574cf6d3ca69d630993d43f9d Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 15:46:43 +0200 Subject: [PATCH 19/39] use .then when possible --- deploy/0_deploy.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index d72bc664d..9bb03f2d7 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -227,9 +227,10 @@ async function getOrDeployRlc(token: string, owner: SignerWithAddress) { return token; } else { console.log('Deploying new RLC token...'); - const rlcContract = await rlcFactory.deploy(); - await rlcContract.waitForDeployment(); - const rlcAddress = await rlcContract.getAddress(); + const rlcAddress = await rlcFactory + .deploy() + .then((contract) => contract.waitForDeployment()) + .then((contract) => contract.getAddress()); // Save the deployment to hardhat deployments await deployments.save('RLC', { From 06732be7f928eb6e16f92ab1d3b4fa9b500feba6 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 15:57:50 +0200 Subject: [PATCH 20/39] remove knowned chain to customChains --- hardhat.config.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index f612f51cc..80c686c92 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -242,22 +242,6 @@ const config: HardhatUserConfig = { bellecour: 'nothing', // a non-empty string is needed by the plugin. }, customChains: [ - { - network: 'fuji', - chainId: 43113, - urls: { - apiURL: 'https://api-testnet.snowtrace.io/api', - browserURL: 'https://testnet.snowtrace.io/', - }, - }, - { - network: 'arbitrumSepolia', - chainId: 421614, - urls: { - apiURL: 'https://api-sepolia.arbiscan.io/api', - browserURL: 'https://sepolia.arbiscan.io/', - }, - }, { network: 'viviani', chainId: 133, From 2237a04023b1dca517db00976a248590c2a45c72 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 16:15:35 +0200 Subject: [PATCH 21/39] remove useless verify sections for fuji and sepolia arbitrum network --- hardhat.config.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 80c686c92..10a074888 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -179,19 +179,13 @@ const config: HardhatUserConfig = { }, }, // Add Fuji as a network - fuji: { + avalancheFujiTestnet: { chainId: 43113, url: process.env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', accounts: { mnemonic: process.env.MNEMONIC || '', }, ...fujiBaseConfig, - verify: { - etherscan: { - apiUrl: 'https://api-testnet.snowtrace.io', - apiKey: process.env.SNOWTRACE_API_KEY || '', - }, - }, }, // Add Arbitrum Sepolia as a network 'arbitrum-sepolia': { @@ -201,12 +195,6 @@ const config: HardhatUserConfig = { mnemonic: process.env.MNEMONIC || '', }, ...arbitrumSepoliaBaseConfig, - verify: { - etherscan: { - apiUrl: 'https://api-sepolia.arbiscan.io', - apiKey: process.env.ARBISCAN_API_KEY || '', - }, - }, }, viviani: { chainId: 133, @@ -235,8 +223,7 @@ const config: HardhatUserConfig = { etherscan: { apiKey: { mainnet: process.env.ETHERSCAN_API_KEY || '', - fuji: process.env.SNOWTRACE_API_KEY || '', - avalancheFujiTestnet: process.env.SNOWTRACE_API_KEY || '', + avalancheFujiTestnet: 'nothing', // a non-empty string is needed by the plugin. arbitrumSepolia: process.env.ARBISCAN_API_KEY || '', viviani: 'nothing', // a non-empty string is needed by the plugin. bellecour: 'nothing', // a non-empty string is needed by the plugin. From 1d428be7380b1101480442e31e00cec633436ad8 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Thu, 10 Apr 2025 16:52:24 +0200 Subject: [PATCH 22/39] use npx hardhat --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7b562a681..37ad23ba4 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "check-storage-layout": "npx hardhat run scripts/check-storage.ts", "clean:hardhat": "rm -rf deployments/hardhat", "test": "npm run clean:hardhat && REPORT_GAS=true npx hardhat test", - "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npm run test", - "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npm run test", + "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npx hardhat test", + "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npx hardhat test", "test:native": "npm run clean:hardhat && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", "coverage": "npx hardhat coverage", "verify": "npx hardhat verify", From 76cf6c4f6233da5b28502058bc07bfa2cf672f4f Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 10:52:42 +0200 Subject: [PATCH 23/39] update public fuji rpc to make it work --- hardhat.config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 10a074888..1aaac9c0c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -99,9 +99,7 @@ const config: HardhatUserConfig = { }), ...(isFujiFork && { forking: { - url: - process.env.FUJI_RPC_URL || - 'https://avalanche-fuji-c-chain-rpc.publicnode.com', + url: process.env.FUJI_RPC_URL || 'https://avalanche-fuji.drpc.org', blockNumber: process.env.FUJI_BLOCK_NUMBER ? parseInt(process.env.FUJI_BLOCK_NUMBER) : undefined, From c1bdebc32869d0ed4eab90ca0e4efbeda0245dc2 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 11:37:22 +0200 Subject: [PATCH 24/39] Refactors RLC deployment logic --- deploy/0_deploy.ts | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 9bb03f2d7..300d0284c 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -216,31 +216,25 @@ export default async function deploy() { async function getOrDeployRlc(token: string, owner: SignerWithAddress) { const rlcFactory = new RLC__factory().connect(owner); + let rlcAddress: string; + if (token) { console.log(`Using existing RLC token at: ${token}`); - await deployments.save('RLC', { - abi: (rlcFactory as any).constructor.abi, - address: token, - bytecode: (await rlcFactory.getDeployTransaction()).data, - deployedBytecode: await ethers.provider.getCode(token), - }); - return token; + rlcAddress = token; } else { console.log('Deploying new RLC token...'); - const rlcAddress = await rlcFactory + rlcAddress = await rlcFactory .deploy() .then((contract) => contract.waitForDeployment()) .then((contract) => contract.getAddress()); - - // Save the deployment to hardhat deployments - await deployments.save('RLC', { - abi: (rlcFactory as any).constructor.abi, - address: rlcAddress, - bytecode: (await rlcFactory.getDeployTransaction()).data, - deployedBytecode: await ethers.provider.getCode(rlcAddress), - }); - console.log(`New RLC token deployed at: ${rlcAddress}`); - return rlcAddress; } + + await deployments.save('RLC', { + abi: (rlcFactory as any).constructor.abi, + address: rlcAddress, + bytecode: (await rlcFactory.getDeployTransaction()).data, + deployedBytecode: await ethers.provider.getCode(rlcAddress), + }); + return rlcAddress; } From c671acd433aaee48ffd1cb734d0653930eaa5cd5 Mon Sep 17 00:00:00 2001 From: gfournieriExec <100280020+gfournieriExec@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:21:43 +0200 Subject: [PATCH 25/39] Update CHANGELOG.md Co-authored-by: Zied Guesmi <26070035+zguesmi@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0a05c1d..e9ed8499c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - [x] `IexecPoco2Delegate.sol` ### Features -- Add CreateX factory for new chain X deployment (#215) +- Add CreateX factory for new chain deployment (#215) - Housekeeping (#207, ) - Add Halborn "Poco v5.5 & Voucher v1.0" audit report (#205) - Refactor Factory deployer (#206) From 55583583faf462c0d730c465fd793a947ff66cca Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 14:46:17 +0200 Subject: [PATCH 26/39] use rpc recommended by Zied --- hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 1aaac9c0c..d21a9db4b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -99,7 +99,7 @@ const config: HardhatUserConfig = { }), ...(isFujiFork && { forking: { - url: process.env.FUJI_RPC_URL || 'https://avalanche-fuji.drpc.org', + url: process.env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', blockNumber: process.env.FUJI_BLOCK_NUMBER ? parseInt(process.env.FUJI_BLOCK_NUMBER) : undefined, From f6c8f222bb4284fb6866f89121c60c5ba6f5fa7f Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 14:50:33 +0200 Subject: [PATCH 27/39] Refactor loadHardhatFixtureDeployment logic for improved readability --- test/utils/hardhat-fixture-deployer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index 86517c7a5..7deea36e6 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -117,9 +117,9 @@ async function setUpTokenFork() { export const loadHardhatFixtureDeployment = async () => { if (process.env.LOCAL_FORK == 'true') { return await loadFixture(setUpLocalFork); - } else if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { + } + if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { return await loadFixture(setUpTokenFork); - } else { - return await loadFixture(deployAll); } + return await loadFixture(deployAll); }; From fd50b3f405cd0a16276dda6bf951e06eb7d79dbf Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 14:51:13 +0200 Subject: [PATCH 28/39] update setUpTokenFork name --- test/utils/hardhat-fixture-deployer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index 7deea36e6..b9d05cdaa 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -54,7 +54,7 @@ async function setUpLocalFork() { return proxyAddress; } -async function setUpTokenFork() { +async function setUpLocalForkInTokenMode() { const chainId = (await ethers.provider.getNetwork()).chainId; const chainConfig = config.getChainConfig(chainId); const rlcTokenAddress = chainConfig.token; @@ -119,7 +119,7 @@ export const loadHardhatFixtureDeployment = async () => { return await loadFixture(setUpLocalFork); } if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { - return await loadFixture(setUpTokenFork); + return await loadFixture(setUpLocalForkInTokenMode); } return await loadFixture(deployAll); }; From 39b36e3252d4e95b16d984d68540c7657d05c2f5 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 15:26:07 +0200 Subject: [PATCH 29/39] Refactor local fork setup by extracting account funding and proxy ownership transfer into separate functions --- test/utils/hardhat-fixture-deployer.ts | 98 +++++++++++--------------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index b9d05cdaa..0f50ca68d 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -16,31 +16,9 @@ async function deployAll() { return (await deployments.get('ERC1538Proxy')).address; } -async function setUpLocalFork() { - const chainId = (await ethers.provider.getNetwork()).chainId; - const proxyAddress = config.getChainConfig(chainId).v5.ERC1538Proxy; - if (!proxyAddress) { - throw new Error('ERC1538Proxy is required'); - } - // Send RLCs to default accounts - const srlcRichSigner = await ethers.getImpersonatedSigner(proxyAddress); - const otherAccountInitAmount = - 10 * // Give this much RLC per account - 10 ** 9; +async function transferProxyOwnership(proxyAddress: string) { const accounts = await getIexecAccounts(); - const accountsArray = Object.values(accounts) as SignerWithAddress[]; - console.log(`Rich account ${srlcRichSigner.address} sending RLCs to other accounts..`); const iexecPoco = IexecInterfaceNative__factory.connect(proxyAddress, ethers.provider); - for (let i = 0; i < accountsArray.length; i++) { - const account = accountsArray[i]; - await iexecPoco - .connect(srlcRichSigner) - .transfer(account.address, otherAccountInitAmount) - .then((tx) => tx.wait()); - const balance = await iexecPoco.balanceOf(account.address); - console.log(`Account #${i}: ${account.address} (${balance.toLocaleString()} nRLC)`); - } - // Transfer ownership from Timelock to iexecAdmin EOA account const timelockAddress = await iexecPoco.owner(); const timelock = await ethers.getImpersonatedSigner(timelockAddress); const newIexecAdminAddress = accounts.iexecAdmin.address; @@ -51,6 +29,44 @@ async function setUpLocalFork() { .connect(timelock) .transferOwnership(newIexecAdminAddress) .then((tx) => tx.wait()); +} + +async function fundAccounts(tokenAddress: string, richmanAddress: string, isNativeMode: boolean) { + const accounts = await getIexecAccounts(); + const otherAccountInitAmount = isNativeMode ? 10 * 10 ** 9 : 10000 * 10 ** 9; + const accountsArray = Object.values(accounts) as SignerWithAddress[]; + if (!isNativeMode) { + await ethers.provider.send('hardhat_setBalance', [ + richmanAddress, + '0x1000000000000000000', // 1 ETH + ]); + } + console.log(`Rich account ${richmanAddress} sending RLCs to other accounts..`); + const richmanSigner = await ethers.getImpersonatedSigner(richmanAddress); + const tokenContract = isNativeMode + ? IexecInterfaceNative__factory.connect(tokenAddress, ethers.provider) + : IexecInterfaceToken__factory.connect(tokenAddress, ethers.provider); + for (let i = 0; i < accountsArray.length; i++) { + const account = accountsArray[i]; + await tokenContract + .connect(richmanSigner) + .transfer(account.address, otherAccountInitAmount) + .then((tx) => tx.wait()); + + const balance = await tokenContract.balanceOf(account.address); + console.log(`Account #${i}: ${account.address} (${balance.toLocaleString()} nRLC)`); + } +} + +async function setUpLocalFork() { + const chainId = (await ethers.provider.getNetwork()).chainId; + const proxyAddress = config.getChainConfig(chainId).v5.ERC1538Proxy; + if (!proxyAddress) { + throw new Error('ERC1538Proxy is required'); + } + await fundAccounts(proxyAddress, proxyAddress, true); + await transferProxyOwnership(proxyAddress); + return proxyAddress; } @@ -59,46 +75,14 @@ async function setUpLocalForkInTokenMode() { const chainConfig = config.getChainConfig(chainId); const rlcTokenAddress = chainConfig.token; const richmanAddress = chainConfig.richman; - const accounts = await getIexecAccounts(); if (rlcTokenAddress && richmanAddress) { - const rlcToken = IexecInterfaceToken__factory.connect(rlcTokenAddress, ethers.provider); - const richmanSigner = await ethers.getImpersonatedSigner(richmanAddress); - await ethers.provider.send('hardhat_setBalance', [ - richmanAddress, - '0x1000000000000000000', // 1 ETH - ]); - const otherAccountInitAmount = 10000 * 10 ** 9; - const accountsArray = Object.values(accounts) as SignerWithAddress[]; - console.log(`Rich account ${richmanAddress} sending RLCs to other accounts..`); - - // Transfer RLC tokens to all accounts - for (let i = 0; i < accountsArray.length; i++) { - const account = accountsArray[i]; - await rlcToken - .connect(richmanSigner) - .transfer(account.address, otherAccountInitAmount) - .then((tx) => tx.wait()); - const balance = await rlcToken.balanceOf(account.address); - console.log(`Account #${i}: ${account.address} (${balance.toLocaleString()} nRLC)`); - } + await fundAccounts(rlcTokenAddress, richmanAddress, false); } const proxyAddress = chainConfig.v5.ERC1538Proxy; if (proxyAddress) { console.log(`Using existing ERC1538Proxy at ${proxyAddress}`); - const iexecPoco = IexecInterfaceToken__factory.connect(proxyAddress, ethers.provider); - const timelockAddress = await iexecPoco.owner(); - const timelock = await ethers.getImpersonatedSigner(timelockAddress); - const newIexecAdminAddress = accounts.iexecAdmin.address; - console.log( - `Transferring Poco ownership from Timelock:${timelockAddress} to iexecAdmin:${newIexecAdminAddress}`, - ); - - await iexecPoco - .connect(timelock) - .transferOwnership(newIexecAdminAddress) - .then((tx) => tx.wait()); - + await transferProxyOwnership(proxyAddress); return proxyAddress; } else { console.log('No existing ERC1538Proxy found, deploying new contracts'); From d2b00ddbec97fa431117e5c1e5c3fb80cd62bd41 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 16:53:19 +0200 Subject: [PATCH 30/39] Remove 'usefactory' parameter from v5 configuration and update related logic for factory address handling and deployment --- README.md | 2 +- config/config.json | 11 ------- config/config_native.json | 1 - config/config_token.json | 1 - deploy/0_deploy.ts | 3 +- scripts/deploy-timelock.ts | 3 +- utils/FactoryDeployer.ts | 67 ++++++++++++++++++++++++-------------- utils/config.ts | 1 - 8 files changed, 45 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 93fea986f..3221d8a7d 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ It contains: - **"asset":** can be "Token" or "Native", select which escrow to use. - **"token":** the address of the token to use. If asset is set to token, and no token address is provided, a mock will be deployed on the fly. - **"v3":** a list of resources from a previous (v3) deployment. This allows previous resources to be automatically available. It also enables score transfer from v3 to v5. [optional] - - **"v5":** deployment parameters for the new version. If usefactory is set to true, and no salt is provided, `bytes32(0)` will be used by default. + - **"v5":** deployment parameters for the new version. If factory address is set, and no salt is provided, `bytes32(0)` will be used by default. If you want to deploy the iExec PoCo V5 smart contracts on a new blockchain, the recommended process is to: diff --git a/config/config.json b/config/config.json index 7d0bddbb5..ab15f969e 100644 --- a/config/config.json +++ b/config/config.json @@ -44,7 +44,6 @@ "WorkerpoolRegistry": "0xc398052563469e6Ea7C442aBf124aADE7ec2CC92" }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", "AppRegistry": "0xB1C52075b276f87b1834919167312221d50c9D16", "DatasetRegistry": "0x799DAa22654128d0C64d5b79eac9283008158730", @@ -63,7 +62,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -79,7 +77,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -96,7 +93,6 @@ "WorkerpoolRegistry": "0xdAD30AAb14F569830bFd26EdF72df876dc30D20c" }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -112,7 +108,6 @@ "WorkerpoolRegistry": "0x3f4C18C322064576C048b1284b700288ffEf126B" }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -126,7 +121,6 @@ "WorkerpoolRegistry": "0x1Cae59C7745A61dD37CD17f174745959D0f3f400" }, "v5": { - "usefactory": true, "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -142,7 +136,6 @@ "v5": { "ERC1538Proxy": "0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f", "IexecLibOrders_v5": "0xE8b04c85C47fcEc0e9eE30D4034e2997f6519123", - "usefactory": true, "factory": "0xfAC000a12dA42B871c0AaD5F25391aAe62958Db1", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } @@ -160,7 +153,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } @@ -176,7 +168,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } @@ -193,8 +184,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, - "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } } diff --git a/config/config_native.json b/config/config_native.json index 6b31e5d38..6e65aec90 100644 --- a/config/config_native.json +++ b/config/config_native.json @@ -43,7 +43,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "salt": "0xbe1a000000000000000000000000000000000000000000000000000000000000" } } diff --git a/config/config_token.json b/config/config_token.json index a886c8266..8411bfc42 100644 --- a/config/config_token.json +++ b/config/config_token.json @@ -43,7 +43,6 @@ "WorkerpoolRegistry": null }, "v5": { - "usefactory": true, "salt": "0xbe1a000000000000000000000000000000000000000000000000000000000000" } } diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 300d0284c..f8646f359 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -57,8 +57,7 @@ export default async function deploy() { const [owner] = await ethers.getSigners(); const deploymentOptions = config.getChainConfigOrDefault(chainId); const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.ZeroHash; - const factoryAddress = - process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory || ethers.ZeroAddress; + const factoryAddress = process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory; const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy RLC const isTokenMode = !config.isNativeChain(deploymentOptions); diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index f0521ee7a..7d998f3fa 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -19,8 +19,7 @@ export const deploy = async () => { const salt = process.env.SALT || deploymentOptions.v5.salt; // Initialize factory deployer - const factoryAddress = - process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory || ethers.ZeroAddress; + const factoryAddress = process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory; const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); // Deploy TimelockController diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 6e555485c..354c5b4d5 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -11,11 +11,11 @@ import { getBaseNameFromContractFactory } from './deploy-tools'; export class FactoryDeployer { owner: SignerWithAddress; salt: string; - factoryAddress: string; + factoryAddress?: string; createX!: ICreateX; genericFactory!: GenericFactory; - constructor(owner: SignerWithAddress, salt: string, factoryAddress: string) { + constructor(owner: SignerWithAddress, salt: string, factoryAddress?: string) { this.owner = owner; this.salt = salt; this.factoryAddress = factoryAddress; @@ -119,6 +119,9 @@ export class FactoryDeployer { if (this.genericFactory) { return; } + if (!this.factoryAddress) { + throw new Error('Factory address not set'); + } this.genericFactory = GenericFactory__factory.connect(this.factoryAddress, this.owner); if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { console.log(`→ Factory is available on this network`); @@ -130,30 +133,44 @@ export class FactoryDeployer { if (this.createX) { return; } - this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); - if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { - console.log(`→ CreateX is available on this network at ${this.factoryAddress}`); - return; - } - // Use full in case of working with local hardhat network, or bellecour - try { - console.log(`→ Factory is not yet deployed on this network`); - const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); - const deployer = factorySignedTx.from; - const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); - const tx = factorySignedTxJson; + if (!this.factoryAddress) { + // Use full in case of working with local hardhat network, or bellecour + try { + console.log(`→ Factory is not yet deployed on this network`); + const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); + const deployer = factorySignedTx.from; + const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); + const tx = factorySignedTxJson; + + await this.owner + .sendTransaction({ + to: deployer, + value: cost, + }) + .then((tx) => tx.wait()); + await ethers.provider.broadcastTransaction(tx).then((tx) => tx.wait()); - await this.owner - .sendTransaction({ - to: deployer, - value: cost, - }) - .then((tx) => tx.wait()); - await ethers.provider.broadcastTransaction(tx).then((tx) => tx.wait()); - console.log(`→ Factory successfully deployed`); - } catch (e) { - console.log(e); - throw new Error('→ Error deploying the factory'); + // Calculate the deployed contract address + // For a contract creation transaction, the address is determined by the sender and nonce + const createdContractAddress = ethers.getCreateAddress({ + from: factorySignedTx.from!, + nonce: factorySignedTx.nonce, + }); + console.log( + `→ Factory successfully deployed at address: ${createdContractAddress}`, + ); + this.factoryAddress = createdContractAddress; + this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); + } catch (e) { + console.log(e); + throw new Error('→ Error deploying the factory'); + } + } else { + this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); + if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { + console.log(`→ CreateX is available on this network at ${this.factoryAddress}`); + return; + } } } } diff --git a/utils/config.ts b/utils/config.ts index 40f557eb7..e7c90fd63 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -49,7 +49,6 @@ type ChainConfig = { WorkerpoolRegistry: string | null; }; v5: { - usefactory: boolean; factory?: string; salt: string; AppRegistry?: string; From 5e8c2f2641132b483567a61cf00e8a3b3a5d0881 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Fri, 11 Apr 2025 17:29:52 +0200 Subject: [PATCH 31/39] Add chainId configuration for Fuji and Arbitrum Sepolia networks --- hardhat.config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index d21a9db4b..7a5353b36 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -35,6 +35,7 @@ const fujiBaseConfig = { hardfork: 'london', // Avalanche C-Chain supports EIP-1559 gasPrice: 25_000_000_000, // 25 Gwei default blockGasLimit: 8_000_000, + chainId: 43113, }; // Arbitrum Sepolia specific configuration @@ -42,6 +43,7 @@ const arbitrumSepoliaBaseConfig = { hardfork: 'london', gasPrice: 100_000_000, // 0.1 Gwei default (Arbitrum has lower gas prices) blockGasLimit: 30_000_000, // Arbitrum has higher block gas limits + chainId: 421614, }; const settings = { @@ -104,7 +106,6 @@ const config: HardhatUserConfig = { ? parseInt(process.env.FUJI_BLOCK_NUMBER) : undefined, }, - chainId: 43113, ...fujiBaseConfig, }), ...(isArbitrumSepoliaFork && { @@ -116,7 +117,6 @@ const config: HardhatUserConfig = { ? parseInt(process.env.ARBITRUM_SEPOLIA_BLOCK_NUMBER) : undefined, }, - chainId: 421614, // Arbitrum Sepolia chain ID ...arbitrumSepoliaBaseConfig, }), }, @@ -133,12 +133,10 @@ const config: HardhatUserConfig = { }), ...(isFujiFork && { accounts: 'remote', // Override defaults accounts for impersonation - chainId: 43113, ...fujiBaseConfig, }), ...(isArbitrumSepoliaFork && { accounts: 'remote', // Override defaults accounts for impersonation - chainId: 421614, ...arbitrumSepoliaBaseConfig, }), }, @@ -178,19 +176,21 @@ const config: HardhatUserConfig = { }, // Add Fuji as a network avalancheFujiTestnet: { - chainId: 43113, url: process.env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', accounts: { - mnemonic: process.env.MNEMONIC || '', + mnemonic: + process.env.MNEMONIC || + 'test test test test test test test test test test test junk', }, ...fujiBaseConfig, }, // Add Arbitrum Sepolia as a network 'arbitrum-sepolia': { - chainId: 421614, url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', accounts: { - mnemonic: process.env.MNEMONIC || '', + mnemonic: + process.env.MNEMONIC || + 'test test test test test test test test test test test junk', }, ...arbitrumSepoliaBaseConfig, }, From 1be040ad7b90323fdab69a0b158dff8f083bd211 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 14 Apr 2025 09:23:26 +0200 Subject: [PATCH 32/39] Refactor test scripts to use a unified clean command --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 37ad23ba4..bdb24a231 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ "deploy": "npx hardhat deploy", "deploy:timelock": "hardhat run scripts/deploy-timelock.ts", "check-storage-layout": "npx hardhat run scripts/check-storage.ts", - "clean:hardhat": "rm -rf deployments/hardhat", - "test": "npm run clean:hardhat && REPORT_GAS=true npx hardhat test", - "test:arbitrum-sepolia": "npm run clean:hardhat && ARBITRUM_SEPOLIA_FORK=true npx hardhat test", - "test:fuji": "npm run clean:hardhat && FUJI_FORK=true npx hardhat test", - "test:native": "npm run clean:hardhat && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", + "clean": "rm -rf deployments/hardhat", + "test": "npm run clean && REPORT_GAS=true npx hardhat test", + "test:arbitrum-sepolia": "npm run clean && ARBITRUM_SEPOLIA_FORK=true npx hardhat test", + "test:fuji": "npm run clean && FUJI_FORK=true npx hardhat test", + "test:native": "npm run clean && TEST__IS_NATIVE_CHAIN=true npx hardhat test test/byContract/IexecEscrow/IexecEscrowNative.test.ts", "coverage": "npx hardhat coverage", "verify": "npx hardhat verify", "format": "npx prettier --write", From f06e9df64d8c96f5ae523304ed65e1c66b28b318 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 14 Apr 2025 14:29:28 +0200 Subject: [PATCH 33/39] Refactor FactoryDeployer to unify contract deployment method and improve factory initialization --- deploy/0_deploy.ts | 14 ++-- scripts/deploy-timelock.ts | 2 +- utils/FactoryDeployer.ts | 149 +++++++++++++++++++++++++------------ 3 files changed, 108 insertions(+), 57 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index f8646f359..077bb2d97 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -66,7 +66,7 @@ export default async function deploy() { : ZeroAddress; // native console.log(`RLC: ${rlcInstanceAddress}`); // Deploy ERC1538 proxy contracts - const erc1538UpdateAddress = await factoryDeployer.deployWithFactory( + const erc1538UpdateAddress = await factoryDeployer.deployContract( new ERC1538UpdateDelegate__factory(), ); const transferOwnershipCall = await Ownable__factory.connect( @@ -78,7 +78,7 @@ export default async function deploy() { .catch(() => { throw new Error('Failed to prepare transferOwnership data'); }); - const erc1538ProxyAddress = await factoryDeployer.deployWithFactory( + const erc1538ProxyAddress = await factoryDeployer.deployContract( new ERC1538Proxy__factory(), [erc1538UpdateAddress], transferOwnershipCall, @@ -86,7 +86,7 @@ export default async function deploy() { const erc1538: ERC1538Update = ERC1538Update__factory.connect(erc1538ProxyAddress, owner); console.log(`IexecInstance found at address: ${await erc1538.getAddress()}`); // Deploy library & modules - const iexecLibOrdersAddress = await factoryDeployer.deployWithFactory( + const iexecLibOrdersAddress = await factoryDeployer.deployContract( new IexecLibOrders_v5__factory(), ); const iexecLibOrders = { @@ -113,7 +113,7 @@ export default async function deploy() { new IexecPocoBoostAccessorsDelegate__factory(), ]; for (const module of modules) { - const address = await factoryDeployer.deployWithFactory(module); + const address = await factoryDeployer.deployContract(module); await linkContractToProxy(erc1538, address, module); } // Verify linking on ERC1538Proxy @@ -127,17 +127,17 @@ export default async function deploy() { const [method, , contract] = await erc1538QueryInstance.functionByIndex(i); console.log(`[${i}] ${contract} ${method}`); } - const appRegistryAddress = await factoryDeployer.deployWithFactory( + const appRegistryAddress = await factoryDeployer.deployContract( new AppRegistry__factory(), [], transferOwnershipCall, ); - const datasetRegistryAddress = await factoryDeployer.deployWithFactory( + const datasetRegistryAddress = await factoryDeployer.deployContract( new DatasetRegistry__factory(), [], transferOwnershipCall, ); - const workerpoolRegistryAddress = await factoryDeployer.deployWithFactory( + const workerpoolRegistryAddress = await factoryDeployer.deployContract( new WorkerpoolRegistry__factory(), [], transferOwnershipCall, diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index 7d998f3fa..2ca2a98b0 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -40,7 +40,7 @@ export const deploy = async () => { ]; const constructorArgs = [ONE_WEEK_IN_SECONDS, ADMINISTRATORS, PROPOSERS, EXECUTORS]; const timelockFactory = new TimelockController__factory(owner); - await factoryDeployer.deployWithFactory(timelockFactory, constructorArgs); + await factoryDeployer.deployContract(timelockFactory, constructorArgs); }; if (require.main === module) { diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 354c5b4d5..9cec64579 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -8,28 +8,54 @@ import { deployments, ethers } from 'hardhat'; import { GenericFactory, GenericFactory__factory, ICreateX, ICreateX__factory } from '../typechain'; import { getBaseNameFromContractFactory } from './deploy-tools'; +// Define factory types +type FactoryType = 'createx' | 'generic'; + export class FactoryDeployer { owner: SignerWithAddress; salt: string; factoryAddress?: string; - createX!: ICreateX; - genericFactory!: GenericFactory; + factoryType: FactoryType; + factory?: ICreateX | GenericFactory; - constructor(owner: SignerWithAddress, salt: string, factoryAddress?: string) { + constructor( + owner: SignerWithAddress, + salt: string, + factoryAddress?: string, + factoryType: FactoryType = 'createx', + ) { this.owner = owner; this.salt = salt; this.factoryAddress = factoryAddress; + this.factoryType = factoryType; + } + + /** + * Deploy a contract through the configured factory [and optionally trigger a call] + */ + async deployContract( + contractFactory: ContractFactory, + constructorArgs?: any[], + call?: string, + ): Promise { + await this.initFactory(); + + if (this.factoryType === 'createx') { + return this.deployWithCreateX(contractFactory, constructorArgs, call); + } else { + return this.deployWithGenericFactory(contractFactory, constructorArgs, call); + } } /** * Deploy a contract through CreateX [and optionally trigger a call] */ - async deployWithFactory( + private async deployWithCreateX( contractFactory: ContractFactory, constructorArgs?: any[], call?: string, - ) { - await this.initCreateX(); + ): Promise { + const createX = this.factory as ICreateX; let bytecode = (await contractFactory.getDeployTransaction(...(constructorArgs ?? []))) .data; if (!bytecode) { @@ -37,7 +63,7 @@ export class FactoryDeployer { } const initCodeHash = ethers.keccak256(bytecode); const saltHash = ethers.keccak256(this.salt); - const contractAddress = await this.createX['computeCreate2Address(bytes32,bytes32)']( + const contractAddress = await createX['computeCreate2Address(bytes32,bytes32)']( saltHash, initCodeHash, ); @@ -46,7 +72,7 @@ export class FactoryDeployer { if (!previouslyDeployed) { await ( call - ? this.createX['deployCreate2AndInit(bytes32,bytes,bytes,(uint256,uint256))']( + ? createX['deployCreate2AndInit(bytes32,bytes,bytes,(uint256,uint256))']( this.salt, bytecode, call, @@ -55,88 +81,113 @@ export class FactoryDeployer { initCallAmount: 0, }, ) - : this.createX['deployCreate2(bytes32,bytes)'](this.salt, bytecode) + : createX['deployCreate2(bytes32,bytes)'](this.salt, bytecode) ).then((tx) => tx.wait()); } - const contractName = getBaseNameFromContractFactory(contractFactory); - console.log( - `${contractName}: ${contractAddress} ${ - previouslyDeployed ? ' (previously deployed)' : '' - }`, + await this.saveDeployment( + contractFactory, + contractAddress, + bytecode, + constructorArgs, + previouslyDeployed, ); - await deployments.save(contractName, { - // abi field is not used but is a required arg. Empty abi would be fine - abi: (contractFactory as any).constructor.abi, - address: contractAddress, - bytecode: bytecode, - args: constructorArgs, - }); return contractAddress; } /** * Deploy a contract through GenericFactory [and optionally trigger a call] */ - async deployWithGeneric( + private async deployWithGenericFactory( contractFactory: ContractFactory, constructorArgs?: any[], call?: string, - ) { - await this.initLegacy(); + ): Promise { + const genericFactory = this.factory as GenericFactory; let bytecode = (await contractFactory.getDeployTransaction(...(constructorArgs ?? []))) .data; if (!bytecode) { throw new Error('Failed to prepare bytecode'); } let contractAddress = await (call - ? this.genericFactory.predictAddressWithCall(bytecode, this.salt, call) - : this.genericFactory.predictAddress(bytecode, this.salt)); + ? genericFactory.predictAddressWithCall(bytecode, this.salt, call) + : genericFactory.predictAddress(bytecode, this.salt)); const previouslyDeployed = (await ethers.provider.getCode(contractAddress)) !== '0x'; if (!previouslyDeployed) { await ( call - ? this.genericFactory.createContractAndCall(bytecode, this.salt, call) - : this.genericFactory.createContract(bytecode, this.salt) + ? genericFactory.createContractAndCall(bytecode, this.salt, call) + : genericFactory.createContract(bytecode, this.salt) ).then((tx) => tx.wait()); } + await this.saveDeployment( + contractFactory, + contractAddress, + bytecode, + constructorArgs, + previouslyDeployed, + ); + return contractAddress; + } + + /** + * Save deployment information to Hardhat deployments + */ + private async saveDeployment( + contractFactory: ContractFactory, + address: string, + bytecode: string, + constructorArgs?: any[], + previouslyDeployed: boolean = false, + ): Promise { const contractName = getBaseNameFromContractFactory(contractFactory); console.log( - `${contractName}: ${contractAddress} ${ - previouslyDeployed ? ' (previously deployed)' : '' - }`, + `${contractName}: ${address} ${previouslyDeployed ? ' (previously deployed)' : ''}`, ); await deployments.save(contractName, { // abi field is not used but is a required arg. Empty abi would be fine abi: (contractFactory as any).constructor.abi, - address: contractAddress, + address: address, bytecode: bytecode, args: constructorArgs, }); - return contractAddress; } - private async initLegacy() { - if (this.genericFactory) { - return; + /** + * Initialize the appropriate factory based on factoryType + */ + private async initFactory() { + if (this.factory) { + return; // Factory already initialized + } + if (this.factoryType === 'createx') { + await this.initCreateX(); + } else { + await this.initGenericFactory(); } + } + + /** + * Initialize Generic Factory + */ + private async initGenericFactory() { if (!this.factoryAddress) { - throw new Error('Factory address not set'); + throw new Error('Factory address not set for GenericFactory'); } - this.genericFactory = GenericFactory__factory.connect(this.factoryAddress, this.owner); + this.factory = GenericFactory__factory.connect(this.factoryAddress, this.owner); if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { - console.log(`→ Factory is available on this network`); + console.log(`→ GenericFactory is available on this network at ${this.factoryAddress}`); return; } + throw new Error('GenericFactory not deployed at the provided address'); } + /** + * Initialize CreateX Factory + */ private async initCreateX() { - if (this.createX) { - return; - } if (!this.factoryAddress) { - // Use full in case of working with local hardhat network, or bellecour try { - console.log(`→ Factory is not yet deployed on this network`); + console.log(`→ CreateX is not yet deployed on this network`); const factorySignedTx = ethers.Transaction.from(factorySignedTxJson); const deployer = factorySignedTx.from; const cost = (factorySignedTx.gasPrice! * factorySignedTx.gasLimit!).toString(); @@ -151,26 +202,26 @@ export class FactoryDeployer { await ethers.provider.broadcastTransaction(tx).then((tx) => tx.wait()); // Calculate the deployed contract address - // For a contract creation transaction, the address is determined by the sender and nonce const createdContractAddress = ethers.getCreateAddress({ from: factorySignedTx.from!, nonce: factorySignedTx.nonce, }); console.log( - `→ Factory successfully deployed at address: ${createdContractAddress}`, + `→ CreateX successfully deployed at address: ${createdContractAddress}`, ); this.factoryAddress = createdContractAddress; - this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); + this.factory = ICreateX__factory.connect(this.factoryAddress, this.owner); } catch (e) { console.log(e); - throw new Error('→ Error deploying the factory'); + throw new Error('→ Error deploying CreateX'); } } else { - this.createX = ICreateX__factory.connect(this.factoryAddress, this.owner); + this.factory = ICreateX__factory.connect(this.factoryAddress, this.owner); if ((await ethers.provider.getCode(this.factoryAddress)) !== '0x') { console.log(`→ CreateX is available on this network at ${this.factoryAddress}`); return; } + throw new Error('CreateX not deployed at the provided address'); } } } From 417fa0f540efbfe23f28a50e19ea1748e7af470b Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Mon, 14 Apr 2025 16:08:55 +0200 Subject: [PATCH 34/39] remove useless hardfork specification --- hardhat.config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 7a5353b36..64513012b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -32,7 +32,6 @@ const bellecourBaseConfig = { // Avalanche Fuji specific configuration const fujiBaseConfig = { - hardfork: 'london', // Avalanche C-Chain supports EIP-1559 gasPrice: 25_000_000_000, // 25 Gwei default blockGasLimit: 8_000_000, chainId: 43113, @@ -40,7 +39,6 @@ const fujiBaseConfig = { // Arbitrum Sepolia specific configuration const arbitrumSepoliaBaseConfig = { - hardfork: 'london', gasPrice: 100_000_000, // 0.1 Gwei default (Arbitrum has lower gas prices) blockGasLimit: 30_000_000, // Arbitrum has higher block gas limits chainId: 421614, From 80da38562a54844993009e9b8d5259e6ab3fec49 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 15 Apr 2025 09:03:50 +0200 Subject: [PATCH 35/39] update readme --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301d79064..588b03262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Features - Add CreateX factory for new chain deployment (#215) +- Support Arbitrum & Avalanche Fuji testnets (#215) - Housekeeping (#207, ) - Add Halborn "Poco v5.5 & Voucher v1.0" audit report (#205) - Refactor Factory deployer (#206) From 9c6594646d25761c695ff4c859259d161616a9cf Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 15 Apr 2025 09:05:01 +0200 Subject: [PATCH 36/39] Replace hardcoded mnemonic with HARDHAT_NETWORK_MNEMONIC in Fuji and Arbitrum Sepolia configurations --- hardhat.config.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 64513012b..3364a8bf6 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -176,9 +176,7 @@ const config: HardhatUserConfig = { avalancheFujiTestnet: { url: process.env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', accounts: { - mnemonic: - process.env.MNEMONIC || - 'test test test test test test test test test test test junk', + mnemonic: process.env.MNEMONIC || HARDHAT_NETWORK_MNEMONIC, }, ...fujiBaseConfig, }, @@ -186,9 +184,7 @@ const config: HardhatUserConfig = { 'arbitrum-sepolia': { url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', accounts: { - mnemonic: - process.env.MNEMONIC || - 'test test test test test test test test test test test junk', + mnemonic: process.env.MNEMONIC || HARDHAT_NETWORK_MNEMONIC, }, ...arbitrumSepoliaBaseConfig, }, From c1a0e6eca2bb431b6d0b4bca0e119f1cd823e882 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 15 Apr 2025 09:07:23 +0200 Subject: [PATCH 37/39] Rename setUpLocalFork to setUpLocalForkInNativeMode for clarity and update references in loadHardhatFixtureDeployment --- test/utils/hardhat-fixture-deployer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/hardhat-fixture-deployer.ts b/test/utils/hardhat-fixture-deployer.ts index 0f50ca68d..329af58df 100644 --- a/test/utils/hardhat-fixture-deployer.ts +++ b/test/utils/hardhat-fixture-deployer.ts @@ -58,7 +58,7 @@ async function fundAccounts(tokenAddress: string, richmanAddress: string, isNati } } -async function setUpLocalFork() { +async function setUpLocalForkInNativeMode() { const chainId = (await ethers.provider.getNetwork()).chainId; const proxyAddress = config.getChainConfig(chainId).v5.ERC1538Proxy; if (!proxyAddress) { @@ -100,7 +100,7 @@ async function setUpLocalForkInTokenMode() { */ export const loadHardhatFixtureDeployment = async () => { if (process.env.LOCAL_FORK == 'true') { - return await loadFixture(setUpLocalFork); + return await loadFixture(setUpLocalForkInNativeMode); } if (process.env.FUJI_FORK == 'true' || process.env.ARBITRUM_SEPOLIA_FORK == 'true') { return await loadFixture(setUpLocalForkInTokenMode); From 69be4ab755dd6b55cb9e4f60e1855e731fbf1770 Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 15 Apr 2025 09:21:27 +0200 Subject: [PATCH 38/39] remove useless else --- utils/FactoryDeployer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 9cec64579..19c5c81a8 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -39,12 +39,10 @@ export class FactoryDeployer { call?: string, ): Promise { await this.initFactory(); - if (this.factoryType === 'createx') { - return this.deployWithCreateX(contractFactory, constructorArgs, call); - } else { - return this.deployWithGenericFactory(contractFactory, constructorArgs, call); + return await this.deployWithCreateX(contractFactory, constructorArgs, call); } + return await this.deployWithGenericFactory(contractFactory, constructorArgs, call); } /** From e737a6b3d54fcf085a8323a8a74082d28339882b Mon Sep 17 00:00:00 2001 From: gfournieriExec Date: Tue, 15 Apr 2025 13:56:49 +0200 Subject: [PATCH 39/39] Refactor FactoryDeployer to use chainId and streamline factory initialization; update config for optional factoryType and salt --- config/config.json | 4 ++++ deploy/0_deploy.ts | 4 +--- scripts/deploy-timelock.ts | 8 +------- utils/FactoryDeployer.ts | 22 ++++++++-------------- utils/config.ts | 3 ++- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/config/config.json b/config/config.json index ab15f969e..8c85168ca 100644 --- a/config/config.json +++ b/config/config.json @@ -137,6 +137,7 @@ "ERC1538Proxy": "0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f", "IexecLibOrders_v5": "0xE8b04c85C47fcEc0e9eE30D4034e2997f6519123", "factory": "0xfAC000a12dA42B871c0AaD5F25391aAe62958Db1", + "factoryType": "generic", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -154,6 +155,8 @@ }, "v5": { "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "factoryType": "createx", + "ERC1538Proxy": "0x14B465079537655E1662F012e99EBa3863c8B9E0", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, @@ -169,6 +172,7 @@ }, "v5": { "factory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", + "factoryType": "createx", "salt": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index 077bb2d97..f3ba03857 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -56,9 +56,7 @@ export default async function deploy() { const chainId = (await ethers.provider.getNetwork()).chainId; const [owner] = await ethers.getSigners(); const deploymentOptions = config.getChainConfigOrDefault(chainId); - const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.ZeroHash; - const factoryAddress = process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory; - const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); + const factoryDeployer = new FactoryDeployer(owner, chainId); // Deploy RLC const isTokenMode = !config.isNativeChain(deploymentOptions); let rlcInstanceAddress = isTokenMode diff --git a/scripts/deploy-timelock.ts b/scripts/deploy-timelock.ts index 2ca2a98b0..a1b519a8c 100644 --- a/scripts/deploy-timelock.ts +++ b/scripts/deploy-timelock.ts @@ -5,7 +5,6 @@ import { duration } from '@nomicfoundation/hardhat-network-helpers/dist/src/help import { ethers } from 'hardhat'; import { TimelockController__factory } from '../typechain'; import { FactoryDeployer } from '../utils/FactoryDeployer'; -import config from '../utils/config'; /** * Deploy TimelockController contract using the generic factory. @@ -15,12 +14,7 @@ export const deploy = async () => { console.log('Deploying TimelockController..'); const chainId = (await ethers.provider.getNetwork()).chainId; const [owner] = await ethers.getSigners(); - const deploymentOptions = config.getChainConfigOrDefault(chainId); - const salt = process.env.SALT || deploymentOptions.v5.salt; - - // Initialize factory deployer - const factoryAddress = process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory; - const factoryDeployer = new FactoryDeployer(owner, salt, factoryAddress); + const factoryDeployer = new FactoryDeployer(owner, chainId); // Deploy TimelockController const ONE_WEEK_IN_SECONDS = duration.days(7); diff --git a/utils/FactoryDeployer.ts b/utils/FactoryDeployer.ts index 19c5c81a8..5a3b28afc 100644 --- a/utils/FactoryDeployer.ts +++ b/utils/FactoryDeployer.ts @@ -6,28 +6,22 @@ import factorySignedTxJson from 'createx/scripts/presigned-createx-deployment-tr import { ContractFactory } from 'ethers'; import { deployments, ethers } from 'hardhat'; import { GenericFactory, GenericFactory__factory, ICreateX, ICreateX__factory } from '../typechain'; +import config from '../utils/config'; import { getBaseNameFromContractFactory } from './deploy-tools'; - -// Define factory types -type FactoryType = 'createx' | 'generic'; - export class FactoryDeployer { owner: SignerWithAddress; salt: string; factoryAddress?: string; - factoryType: FactoryType; + factoryType: string; factory?: ICreateX | GenericFactory; - constructor( - owner: SignerWithAddress, - salt: string, - factoryAddress?: string, - factoryType: FactoryType = 'createx', - ) { + constructor(owner: SignerWithAddress, chainId: bigint) { + const deploymentOptions = config.getChainConfigOrDefault(chainId); this.owner = owner; - this.salt = salt; - this.factoryAddress = factoryAddress; - this.factoryType = factoryType; + this.salt = process.env.SALT || deploymentOptions.v5.salt || ethers.ZeroHash; + this.factoryAddress = process.env.FACTORY_ADDRESS || deploymentOptions.v5.factory; + this.factoryType = + process.env.FACTORY_TYPE || deploymentOptions.v5.factoryType || 'createx'; } /** diff --git a/utils/config.ts b/utils/config.ts index e7c90fd63..d7f7ffd68 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -50,7 +50,8 @@ type ChainConfig = { }; v5: { factory?: string; - salt: string; + factoryType?: string; + salt?: string; AppRegistry?: string; DatasetRegistry?: string; WorkerpoolRegistry?: string;