From fd02b4b8e0c19bc6ad9caf9e2e6ea523532a0c96 Mon Sep 17 00:00:00 2001 From: Zied <26070035+zguesmi@users.noreply.github.com> Date: Mon, 28 Jul 2025 18:22:50 +0200 Subject: [PATCH 1/3] feat: Set owner at deployment --- config/config.json | 1 + deploy/0_deploy.ts | 111 +++++++++++++++++++++++++++------------------ utils/config.ts | 1 + 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/config/config.json b/config/config.json index db9f1c8e3..ac7b39152 100644 --- a/config/config.json +++ b/config/config.json @@ -165,6 +165,7 @@ "asset": "Token", "token": "0x86934B9A25212D94fb95486FAE8518d6039f0309", "richman": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3", + "owner": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3", "uniswap": false, "v3": { "Hub": null, diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index dbaf81068..db3d945a9 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -48,33 +48,22 @@ export default async function deploy() { console.log('Deploying PoCo..'); const network = await ethers.provider.getNetwork(); const chainId = network.chainId; - const [owner] = await ethers.getSigners(); + const [deployer] = await ethers.getSigners(); const deploymentOptions = config.getChainConfigOrDefault(chainId); - factoryDeployer = new FactoryDeployer(owner, chainId); + const ownerAddress = deploymentOptions.owner || deployer.address; + factoryDeployer = new FactoryDeployer(deployer, chainId); // Deploy RLC const isTokenMode = !config.isNativeChain(deploymentOptions); let rlcInstanceAddress = isTokenMode - ? await getOrDeployRlc(deploymentOptions.token!, owner) // token + ? await getOrDeployRlc(deploymentOptions.token!, deployer, ownerAddress) // token : ZeroAddress; // native console.log(`RLC: ${rlcInstanceAddress}`); /** * Deploy proxy and facets. */ // TODO put inside init() function. - const transferOwnershipCall = await Ownable__factory.connect( - ZeroAddress, // any is fine - owner, // any is fine - ) - .transferOwnership.populateTransaction(owner.address) - .then((tx) => tx.data) - .catch(() => { - throw new Error('Failed to prepare transferOwnership data'); - }); - const diamondProxyAddress = await deployDiamondProxyWithDefaultFacets( - owner, - // transferOwnershipCall, //TODO - ); - const diamond = DiamondCutFacet__factory.connect(diamondProxyAddress, owner); + const diamondProxyAddress = await deployDiamondProxyWithDefaultFacets(deployer); + const diamond = DiamondCutFacet__factory.connect(diamondProxyAddress, deployer); console.log(`IexecInstance found at address: ${await diamond.getAddress()}`); // Deploy library & facets const iexecLibOrdersAddress = await factoryDeployer.deployContract( @@ -106,7 +95,7 @@ export default async function deploy() { // Verify linking on Diamond Proxy const diamondLoupeFacetInstance: DiamondLoupeFacet = DiamondLoupeFacet__factory.connect( diamondProxyAddress, - owner, + deployer, ); const diamondFacets = await diamondLoupeFacetInstance.facets(); const functionCount = diamondFacets @@ -121,27 +110,24 @@ export default async function deploy() { /** * Deploy registries and link them to the proxy. */ - const appRegistryAddress = await factoryDeployer.deployContract( - new AppRegistry__factory(), - [], - transferOwnershipCall, - ); + const appRegistryAddress = await factoryDeployer.deployContract(new AppRegistry__factory(), []); const datasetRegistryAddress = await factoryDeployer.deployContract( new DatasetRegistry__factory(), [], - transferOwnershipCall, ); const workerpoolRegistryAddress = await factoryDeployer.deployContract( new WorkerpoolRegistry__factory(), [], - transferOwnershipCall, ); - const appRegistryInstance = AppRegistry__factory.connect(appRegistryAddress, owner); - const datasetRegistryInstance = DatasetRegistry__factory.connect(datasetRegistryAddress, owner); + const appRegistryInstance = AppRegistry__factory.connect(appRegistryAddress, deployer); + const datasetRegistryInstance = DatasetRegistry__factory.connect( + datasetRegistryAddress, + deployer, + ); const workerpoolRegistryInstance = WorkerpoolRegistry__factory.connect( workerpoolRegistryAddress, - owner, + deployer, ); // Base URI configuration from config.json const baseURIApp = config.registriesBaseUri.app; @@ -172,11 +158,11 @@ export default async function deploy() { } // Set main configuration - const iexecAccessorsInstance = IexecAccessors__factory.connect(diamondProxyAddress, owner); + const iexecAccessorsInstance = IexecAccessors__factory.connect(diamondProxyAddress, deployer); const iexecInitialized = (await iexecAccessorsInstance.eip712domain_separator()) != ZeroHash; if (!iexecInitialized) { // TODO replace this with DiamondInit.init(). - await IexecConfigurationFacet__factory.connect(diamondProxyAddress, owner) + await IexecConfigurationFacet__factory.connect(diamondProxyAddress, deployer) .configure( rlcInstanceAddress, 'Staked RLC', @@ -193,7 +179,7 @@ export default async function deploy() { const catCountBefore = await iexecAccessorsInstance.countCategory(); for (let i = Number(catCountBefore); i < config.categories.length; i++) { const category = config.categories[i]; - await IexecCategoryManager__factory.connect(diamondProxyAddress, owner) + await IexecCategoryManager__factory.connect(diamondProxyAddress, deployer) .createCategory( category.name, JSON.stringify(category.description), @@ -206,7 +192,15 @@ export default async function deploy() { for (let i = 0; i < Number(catCountAfter); i++) { console.log(`Category ${i}: ${await iexecAccessorsInstance.viewCategory(i)}`); } - + // Transfer ownership of all contracts to the configured owner. + await transferOwnershipOfProxyAndRegistries( + diamondProxyAddress, + appRegistryAddress, + datasetRegistryAddress, + workerpoolRegistryAddress, + deployer, + ownerAddress, + ); if (network.name !== 'hardhat' && network.name !== 'localhost') { console.log('Waiting for block explorer to index the contracts...'); await new Promise((resolve) => setTimeout(resolve, 60000)); @@ -214,8 +208,12 @@ export default async function deploy() { } } -async function getOrDeployRlc(token: string, owner: SignerWithAddress) { - const rlcFactory = new RLC__factory().connect(owner); +async function getOrDeployRlc( + token: string, + deployer: SignerWithAddress, + ownerAddress: string, +): Promise { + const rlcFactory = new RLC__factory().connect(deployer); let rlcAddress: string; if (token) { @@ -228,6 +226,10 @@ async function getOrDeployRlc(token: string, owner: SignerWithAddress) { .then((contract) => contract.waitForDeployment()) .then((contract) => contract.getAddress()); console.log(`New RLC token deployed at: ${rlcAddress}`); + await Ownable__factory.connect(rlcAddress, deployer) + .transferOwnership(ownerAddress) + .then((tx) => tx.wait()); + console.log(`Ownership of RLC token transferred to: ${deployer.address}`); } await deployments.save('RLC', { @@ -243,13 +245,10 @@ async function getOrDeployRlc(token: string, owner: SignerWithAddress) { * Deploys and initializes a Diamond proxy contract with default facets. * @returns The address of the deployed Diamond proxy contract. */ -async function deployDiamondProxyWithDefaultFacets( - owner: SignerWithAddress, - // transferOwnershipCall: string, // TODO -): Promise { +async function deployDiamondProxyWithDefaultFacets(deployer: SignerWithAddress): Promise { const initAddress = await factoryDeployer.deployContract(new DiamondInit__factory()); const initCalldata = DiamondInit__factory.createInterface().encodeFunctionData('init'); - const libDiamondConfig = await getLibDiamondConfigOrEmpty(owner); + const libDiamondConfig = await getLibDiamondConfigOrEmpty(deployer); // Deploy required proxy facets. const facetFactories = [ new DiamondCutFacet__factory(libDiamondConfig), @@ -268,13 +267,35 @@ async function deployDiamondProxyWithDefaultFacets( } // Set diamond constructor arguments const diamondArgs: DiamondArgsStruct = { - owner: owner.address, + owner: deployer.address, init: initAddress, initCalldata: initCalldata, }; - return await factoryDeployer.deployContract( - new Diamond__factory(libDiamondConfig), - [facetCuts, diamondArgs], - // transferOwnershipCall, // TODO - ); + return await factoryDeployer.deployContract(new Diamond__factory(libDiamondConfig), [ + facetCuts, + diamondArgs, + ]); } + +async function transferOwnershipOfProxyAndRegistries( + diamondAddress: string, + appRegistryAddress: string, + datasetRegistryAddress: string, + workerpoolRegistryAddress: string, + deployer: SignerWithAddress, + ownerAddress: string, +) { + for (const contractAddress of [ + diamondAddress, + appRegistryAddress, + datasetRegistryAddress, + workerpoolRegistryAddress, + ]) { + const contractAsOwnable = Ownable__factory.connect(contractAddress, deployer); + const currentOwner = await contractAsOwnable.owner(); + await contractAsOwnable.transferOwnership(ownerAddress).then((tx) => tx.wait()); + console.log( + `Ownership of contract ${contractAddress} transferred from ${currentOwner} to ${ownerAddress}`, + ); + } +} \ No newline at end of file diff --git a/utils/config.ts b/utils/config.ts index 3bf7e0ebc..5cb3505ac 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -40,6 +40,7 @@ type ChainConfig = { asset: string; 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 + owner?: string | null; uniswap?: boolean; etoken?: string; v3: { From dee5fde9851e52230719fabc933bc32ef2e7815e Mon Sep 17 00:00:00 2001 From: Zied <26070035+zguesmi@users.noreply.github.com> Date: Mon, 28 Jul 2025 18:39:47 +0200 Subject: [PATCH 2/3] Add comment --- deploy/0_deploy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index db3d945a9..f82df92a2 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -44,12 +44,16 @@ import { getLibDiamondConfigOrEmpty } from '../utils/tools'; let factoryDeployer: FactoryDeployer; +// TODO: refactor this file to differentiate hardhat deployment from +// other chains deployment. export default async function deploy() { console.log('Deploying PoCo..'); const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const [deployer] = await ethers.getSigners(); const deploymentOptions = config.getChainConfigOrDefault(chainId); + // TODO: remove the fallback on deployer address to avoid deployement + // misconfiguration. const ownerAddress = deploymentOptions.owner || deployer.address; factoryDeployer = new FactoryDeployer(deployer, chainId); // Deploy RLC From d2be60a86b2305c7e3c0cede07ef245b81b8f98e Mon Sep 17 00:00:00 2001 From: Zied <26070035+zguesmi@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:32:13 +0200 Subject: [PATCH 3/3] Fix ownership transfer --- deploy/0_deploy.ts | 53 +++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/deploy/0_deploy.ts b/deploy/0_deploy.ts index f82df92a2..7d7bdf372 100644 --- a/deploy/0_deploy.ts +++ b/deploy/0_deploy.ts @@ -62,10 +62,22 @@ export default async function deploy() { ? await getOrDeployRlc(deploymentOptions.token!, deployer, ownerAddress) // token : ZeroAddress; // native console.log(`RLC: ${rlcInstanceAddress}`); + // Prepare transferOwnership call to the deployer. + // Ownership transfer should be done in the same deployment transaction + // otherwise it is not possible to transfer ownership before hand if the + // factory is set as the owner. + const transferOwnershipToDeployerCall = await Ownable__factory.connect( + ZeroAddress, // any is fine + ethers.provider, + ) + .transferOwnership.populateTransaction(deployer.address) + .then((tx) => tx.data) + .catch(() => { + throw new Error('Failed to prepare transferOwnership data'); + }); /** * Deploy proxy and facets. */ - // TODO put inside init() function. const diamondProxyAddress = await deployDiamondProxyWithDefaultFacets(deployer); const diamond = DiamondCutFacet__factory.connect(diamondProxyAddress, deployer); console.log(`IexecInstance found at address: ${await diamond.getAddress()}`); @@ -114,14 +126,20 @@ export default async function deploy() { /** * Deploy registries and link them to the proxy. */ - const appRegistryAddress = await factoryDeployer.deployContract(new AppRegistry__factory(), []); + const appRegistryAddress = await factoryDeployer.deployContract( + new AppRegistry__factory(), + [], + transferOwnershipToDeployerCall, + ); const datasetRegistryAddress = await factoryDeployer.deployContract( new DatasetRegistry__factory(), [], + transferOwnershipToDeployerCall, ); const workerpoolRegistryAddress = await factoryDeployer.deployContract( new WorkerpoolRegistry__factory(), [], + transferOwnershipToDeployerCall, ); const appRegistryInstance = AppRegistry__factory.connect(appRegistryAddress, deployer); @@ -197,7 +215,7 @@ export default async function deploy() { console.log(`Category ${i}: ${await iexecAccessorsInstance.viewCategory(i)}`); } // Transfer ownership of all contracts to the configured owner. - await transferOwnershipOfProxyAndRegistries( + await transferOwnershipToFinalOwner( diamondProxyAddress, appRegistryAddress, datasetRegistryAddress, @@ -281,25 +299,26 @@ async function deployDiamondProxyWithDefaultFacets(deployer: SignerWithAddress): ]); } -async function transferOwnershipOfProxyAndRegistries( +/** + * Prepares the transferOwnership calls for all provided contracts. + */ +async function transferOwnershipToFinalOwner( diamondAddress: string, appRegistryAddress: string, datasetRegistryAddress: string, workerpoolRegistryAddress: string, - deployer: SignerWithAddress, + currentOwner: SignerWithAddress, ownerAddress: string, ) { - for (const contractAddress of [ - diamondAddress, - appRegistryAddress, - datasetRegistryAddress, - workerpoolRegistryAddress, + for (const contract of [ + { name: 'Diamond', address: diamondAddress }, + { name: 'AppRegistry', address: appRegistryAddress }, + { name: 'DatasetRegistry', address: datasetRegistryAddress }, + { name: 'WorkerpoolRegistry', address: workerpoolRegistryAddress }, ]) { - const contractAsOwnable = Ownable__factory.connect(contractAddress, deployer); - const currentOwner = await contractAsOwnable.owner(); - await contractAsOwnable.transferOwnership(ownerAddress).then((tx) => tx.wait()); - console.log( - `Ownership of contract ${contractAddress} transferred from ${currentOwner} to ${ownerAddress}`, - ); + await Ownable__factory.connect(contract.address, currentOwner) + .transferOwnership(ownerAddress) + .then((tx) => tx.wait()); + console.log(`Owner of ${contract.name}: ${ownerAddress}`); } -} \ No newline at end of file +}