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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## vNEXT
- Add the ability to deploy without truffle fixture. (#58)
- Sponsor match orders. (#57)
- Upgrade Poco1 to solidity `^0.8.0` (#55):
- Migrate to `openzeppelin@v5`
Expand Down
253 changes: 170 additions & 83 deletions deploy/0_deploy.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
// SPDX-FileCopyrightText: 2023-2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import fs from 'fs';
import hre from 'hardhat';
import hre, { ethers } from 'hardhat';
import path from 'path';
import initial_migration from '../migrations/1_initial_migration';
import deploy_token from '../migrations/3_deploy_token';
import deploy_core from '../migrations/4_deploy_core';
import deploy_ens from '../migrations/5_deploy_ens';
import functions from '../migrations/999_functions';
import { getFunctionSignatures } from '../migrations/utils/getFunctionSignatures';
import {
ENSRegistry,
ERC1538Proxy,
AppRegistry__factory,
DatasetRegistry__factory,
ENSIntegrationDelegate__factory,
ERC1538Proxy__factory,
ERC1538Query,
ERC1538QueryDelegate__factory,
ERC1538Query__factory,
ERC1538Update,
ERC1538UpdateDelegate__factory,
ERC1538Update__factory,
IexecLibOrders_v5,
IexecAccessorsABILegacyDelegate__factory,
IexecAccessorsDelegate__factory,
IexecAccessors__factory,
IexecCategoryManagerDelegate__factory,
IexecCategoryManager__factory,
IexecERC20Delegate__factory,
IexecEscrowNativeDelegate__factory,
IexecEscrowTokenDelegate__factory,
IexecLibOrders_v5__factory,
IexecMaintenanceDelegate__factory,
IexecMaintenanceExtraDelegate__factory,
IexecOrderManagementDelegate__factory,
IexecPoco1Delegate__factory,
IexecPoco2Delegate__factory,
IexecPocoBoostAccessorsDelegate__factory,
IexecPocoBoostDelegate__factory,
PublicResolver,
IexecRelayDelegate__factory,
RLC__factory,
WorkerpoolRegistry__factory,
} from '../typechain';
const erc1538Proxy: ERC1538Proxy = hre.artifacts.require('@iexec/solidity/ERC1538Proxy');
const IexecLibOrders: IexecLibOrders_v5 = hre.artifacts.require('IexecLibOrders_v5');
const ensRegistry: ENSRegistry = hre.artifacts.require(
'@ensdomains/ens-contracts/contracts/registry/ENSRegistry',
);
const ensPublicResolver: PublicResolver = hre.artifacts.require(
'@ensdomains/ens-contracts/contracts/registry/PublicResolver',
);
import { Ownable__factory } from '../typechain/factories/@openzeppelin/contracts/access';
import { FactoryDeployerHelper } from '../utils/FactoryDeployerHelper';
import { getBaseNameFromContractFactory } from '../utils/deploy-tools';
interface Category {
name: string;
description: string;
workClockTimeRef: number;
}
const CONFIG = require('../config/config.json');
// TODO: Deploy & setup ENS without hardhat-truffle

/**
* @dev Deploying contracts with `npx hardhat deploy` task brought by
* `hardhat-deploy` plugin.
Expand All @@ -41,82 +59,144 @@ const ensPublicResolver: PublicResolver = hre.artifacts.require(
* features available in it.
*/
module.exports = async function () {
console.log('Deploying PoCo Nominal..');
const accounts = await hre.web3.eth.getAccounts();
await initial_migration();
await deploy_token(accounts);
await deploy_core(accounts);
await deploy_ens(accounts);
// Retrieve proxy address from previous truffle-fixture deployment
const { address: erc1538ProxyAddress } = await erc1538Proxy.deployed();
if (!erc1538ProxyAddress) {
console.error('Failed to retrieve deployed address of ERC1538Proxy');
process.exitCode = 1;
}
console.log(`ERC1538Proxy found: ${erc1538ProxyAddress}`);
// Save addresses of deployed PoCo Nominal contracts for later use
saveDeployedAddress('ERC1538Proxy', erc1538ProxyAddress);

// Save addresses of deployed ENS contracts for later use
const { address: ensRegistryAddress } = await ensRegistry.deployed();
saveDeployedAddress('ENSRegistry', ensRegistryAddress);
const { address: ensPublicResolverAddress } = await ensPublicResolver.deployed();
saveDeployedAddress('ENSPublicResolver', ensPublicResolverAddress);

console.log('Deploying PoCo Boost..');
console.log('Deploying PoCo..');
const chainId = (await ethers.provider.getNetwork()).chainId;
const [owner] = await hre.ethers.getSigners();
const iexecPocoBoostDeployment = await hre.deployments.deploy('IexecPocoBoostDelegate', {
libraries: {
IexecLibOrders_v5: (await IexecLibOrders.deployed()).address,
},
from: owner.address,
log: true,
});
console.log(`IexecPocoBoostDelegate deployed: ${iexecPocoBoostDeployment.address}`);
const IexecPocoBoostAccessorsDeployment = await hre.deployments.deploy(
'IexecPocoBoostAccessorsDelegate',
{
from: owner.address,
log: true,
},
const deploymentOptions = CONFIG.chains[chainId] || CONFIG.chains.default;
const salt = process.env.SALT || deploymentOptions.v5.salt || ethers.constants.HashZero;
const factoryDeployer = new FactoryDeployerHelper(owner, salt);
// Deploy RLC
const isTokenMode = deploymentOptions.asset == 'Token';
let rlcInstanceAddress = isTokenMode
? await getOrDeployRlc(deploymentOptions.token, owner) // token
: ethers.constants.AddressZero; // native
console.log(`RLC: ${rlcInstanceAddress}`);
// Deploy ERC1538 proxy contracts
const erc1538UpdateAddress = await factoryDeployer.deployWithFactory(
new ERC1538UpdateDelegate__factory(),
);
console.log(
`IexecPocoBoostAccessorsDelegate deployed: ${IexecPocoBoostAccessorsDeployment.address}`,
const transferOwnershipCall = await Ownable__factory.connect(
ethers.constants.AddressZero, // any is fine
owner, // any is fine
)
.populateTransaction.transferOwnership(owner.address)
.then((tx) => tx.data)
.catch(() => {
throw new Error('Failed to prepare transferOwnership data');
});
const erc1538ProxyAddress = await factoryDeployer.deployWithFactory(
new ERC1538Proxy__factory(),
[erc1538UpdateAddress],
transferOwnershipCall,
);

// Show proxy functions
await functions(accounts);

// Save addresses of deployed PoCo contracts for later use
saveDeployedAddress('ERC1538Proxy', erc1538ProxyAddress);
const erc1538: ERC1538Update = ERC1538Update__factory.connect(erc1538ProxyAddress, owner);
console.log(`IexecInstance found at address: ${erc1538.address}`);
// Link Boost methods to ERC1538Proxy
await linkContractToProxy(
erc1538,
iexecPocoBoostDeployment.address,
IexecPocoBoostDelegate__factory,
);
await linkContractToProxy(
erc1538,
IexecPocoBoostAccessorsDeployment.address,
IexecPocoBoostAccessorsDelegate__factory,
// Deploy library & modules
const iexecLibOrdersAddress = await factoryDeployer.deployWithFactory(
new IexecLibOrders_v5__factory(),
);
const iexecLibOrders = {
['contracts/libs/IexecLibOrders_v5.sol:IexecLibOrders_v5']: iexecLibOrdersAddress,
};
const modules = [
new ERC1538QueryDelegate__factory(),
new IexecAccessorsDelegate__factory(),
new IexecAccessorsABILegacyDelegate__factory(),
new IexecCategoryManagerDelegate__factory(),
new IexecERC20Delegate__factory(),
isTokenMode
? new IexecEscrowTokenDelegate__factory()
: new IexecEscrowNativeDelegate__factory(),
new IexecMaintenanceDelegate__factory(iexecLibOrders),
new IexecOrderManagementDelegate__factory(iexecLibOrders),
new IexecPoco1Delegate__factory(iexecLibOrders),
new IexecPoco2Delegate__factory(),
new IexecRelayDelegate__factory(),
new ENSIntegrationDelegate__factory(),
new IexecMaintenanceExtraDelegate__factory(),
new IexecPocoBoostDelegate__factory(iexecLibOrders),
new IexecPocoBoostAccessorsDelegate__factory(),
];
for (const module of modules) {
const address = await factoryDeployer.deployWithFactory(module);
await linkContractToProxy(erc1538, address, module);
}
// Verify linking on ERC1538Proxy
const erc1538QueryInstance: ERC1538Query = ERC1538Query__factory.connect(
erc1538ProxyAddress,
owner,
);
const functionCount = await erc1538QueryInstance.totalFunctions();
console.log(`The deployed ERC1538Proxy now supports ${functionCount} functions:`);
await Promise.all(
[...Array(functionCount.toNumber()).keys()].map(async (i) => {
const [method, _, contract] = await erc1538QueryInstance.functionByIndex(i);
if (contract == iexecPocoBoostDeployment.address) {
console.log(`[${i}] ${contract} (IexecPocoBoostDelegate) ${method}`);
}
}),
for (let i = 0; i < functionCount.toNumber(); i++) {
const [method, , contract] = await erc1538QueryInstance.functionByIndex(i);
console.log(`[${i}] ${contract} ${method}`);
}
const appRegistryAddress = await factoryDeployer.deployWithFactory(
new AppRegistry__factory(),
[],
transferOwnershipCall,
);
const datasetRegistryAddress = await factoryDeployer.deployWithFactory(
new DatasetRegistry__factory(),
[],
transferOwnershipCall,
);
const workerpoolRegistryAddress = await factoryDeployer.deployWithFactory(
new WorkerpoolRegistry__factory(),
[],
transferOwnershipCall,
);
// Set main configuration
const iexecAccessorsInstance = IexecAccessors__factory.connect(erc1538ProxyAddress, owner);
const iexecInitialized =
(await iexecAccessorsInstance.eip712domain_separator()) != ethers.constants.HashZero;
if (!iexecInitialized) {
await IexecMaintenanceDelegate__factory.connect(erc1538ProxyAddress, owner)
.configure(
rlcInstanceAddress,
'Staked RLC',
'SRLC',
9, // TODO: generic ?
appRegistryAddress,
datasetRegistryAddress,
workerpoolRegistryAddress,
ethers.constants.AddressZero,
)
.then((tx) => tx.wait());
}
// Set categories
const catCountBefore = await iexecAccessorsInstance.countCategory();
const categories = CONFIG.categories as Category[];
for (let i = catCountBefore.toNumber(); i < categories.length; i++) {
const category = categories[i];
await IexecCategoryManager__factory.connect(erc1538ProxyAddress, owner).createCategory(
category.name,
JSON.stringify(category.description),
category.workClockTimeRef,
);
}
const catCountAfter = await iexecAccessorsInstance.countCategory();
console.log(`countCategory is now: ${catCountAfter} (was ${catCountBefore})`);
for (let i = 0; i < catCountAfter.toNumber(); i++) {
console.log(`Category ${i}: ${await iexecAccessorsInstance.viewCategory(i)}`);
}
};

async function getOrDeployRlc(token: string, owner: SignerWithAddress) {
return token // token
? token
: await new RLC__factory()
.connect(owner)
.deploy()
.then((contract) => {
contract.deployed();
return contract.address;
});
}

/**
* Link a contract to an ERC1538 proxy.
* @param proxy contract to ERC1538 proxy.
Expand All @@ -128,11 +208,18 @@ async function linkContractToProxy(
contractAddress: string,
contractFactory: any,
) {
await proxy.updateContract(
contractAddress,
getFunctionSignatures(contractFactory.abi),
'Linking ' + contractFactory.name,
);
const contractName = getBaseNameFromContractFactory(contractFactory);
await proxy
.updateContract(
contractAddress,
// TODO: Use contractFactory.interface.functions when moving to ethers@v6
// https://github.com/ethers-io/ethers.js/issues/1069
getFunctionSignatures(contractFactory.constructor.abi),
'Linking ' + contractName,
)
.catch(() => {
throw new Error(`Failed to link ${contractName}`);
});
}

// TODO [optional]: Use hardhat-deploy to save addresses automatically
Expand Down
4 changes: 4 additions & 0 deletions migrations/utils/getFunctionSignatures.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

export function getFunctionSignatures(abi: any[]): string;
4 changes: 1 addition & 3 deletions test/000_fullchain-boost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,7 @@ describe('IexecPocoBoostDelegate (IT)', function () {
* @returns deployed address
*/
async function getContractAddress(contractName: string): Promise<string> {
return await (
await hre.artifacts.require(contractName).deployed()
).address;
return (await deployments.get(contractName)).address;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions utils/FactoryDeployer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2020 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-FileCopyrightText: 2020-2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

const { ethers } = require('ethers');
Expand Down Expand Up @@ -141,4 +141,4 @@ class TruffleDeployer extends EthersDeployer {
}
}

module.exports = { EthersDeployer, TruffleDeployer };
module.exports = { EthersDeployer, TruffleDeployer, factoryAddress: FACTORY.address };
58 changes: 58 additions & 0 deletions utils/FactoryDeployerHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

import { ContractFactory } from '@ethersproject/contracts';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { deployments, ethers } from 'hardhat';
import { GenericFactory, GenericFactory__factory } from '../typechain';
import { getBaseNameFromContractFactory } from './deploy-tools';
const { EthersDeployer: Deployer, factoryAddress } = require('../utils/FactoryDeployer');

export class FactoryDeployerHelper {
salt: string;
init: any;
genericFactory: GenericFactory;

constructor(owner: SignerWithAddress, salt: string) {
this.salt = salt;
this.init = new Deployer(owner);
this.genericFactory = GenericFactory__factory.connect(factoryAddress, owner);
}

/**
* Deploy a contract through GenericFactory [and optionally trigger a call]
*/
async deployWithFactory(
contractFactory: ContractFactory,
constructorArgs?: any[],
call?: string,
) {
await this.init.ready(); // Deploy GenericFactory if not already done
let bytecode = 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,
});
return contractAddress;
}
}
Loading