diff --git a/packages/sharing-smart-contract/.env.template b/packages/sharing-smart-contract/.env.template index 4c6302e73..3ba478b51 100644 --- a/packages/sharing-smart-contract/.env.template +++ b/packages/sharing-smart-contract/.env.template @@ -1,5 +1,5 @@ # wallet used for transactions -WALLET_PRIVATE_KEY=... +PRIVATE_KEY=... # environment to use for configuration (prod/staging). The default is prod. ENV=... diff --git a/packages/sharing-smart-contract/README.md b/packages/sharing-smart-contract/README.md index 17cca8ff8..0b1b99946 100644 --- a/packages/sharing-smart-contract/README.md +++ b/packages/sharing-smart-contract/README.md @@ -39,29 +39,26 @@ To verify the contracts: npm run verify ``` -### Deploy (Production) +### Deployment -To deploy the project on the production network - bellecour. -⚠️ Be sure before deploying on bellecour +To deploy the contracts on a local hardhat network, run: ```bash -npm run script:prod +npm run deploy # [-- --network ] if using an external local node. ``` -### Deploy (Test) - -To deploy the project on the test network - localhost. -You need first to start a local hardhat node which will be a fork of bellecour network : - +To deploy the project on a live network, two options are available: +1. Triggering the dedicated Github Action workflow (recommended). +2. Or adding a private key locally and running: ```bash -npx hardhat node +npm run deploy -- --network ``` -Open a new terminal and run : +#### Note: +* Deployment on chains that support CreateX factory will deploy contracts +using `create2` strategy. +* Github Actions workflow should be used for production deployments. -```bash -npm run script:test -``` ### Run Tests diff --git a/packages/sharing-smart-contract/config/config.cjs b/packages/sharing-smart-contract/config/config.cjs new file mode 100644 index 000000000..7f69c1d50 --- /dev/null +++ b/packages/sharing-smart-contract/config/config.cjs @@ -0,0 +1,8 @@ +// Hardhat Ignition does not support ESM modules, so we use CommonJS syntax. +// TODO refactor this to use ESM syntax when Hardhat Ignition supports it. + +module.exports = { + POCO_ADDRESS: '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f', + DATASET_REGISTRY_ADDRESS: '0x799DAa22654128d0C64d5b79eac9283008158730', + APP_REGISTRY_ADDRESS: '0xB1C52075b276f87b1834919167312221d50c9D16', +}; diff --git a/packages/sharing-smart-contract/config/config.js b/packages/sharing-smart-contract/config/config.js index 57689eb0b..635eb0e86 100644 --- a/packages/sharing-smart-contract/config/config.js +++ b/packages/sharing-smart-contract/config/config.js @@ -1,4 +1,5 @@ -export const SMART_CONTRACT_ADDRESS_FILE = '.smart-contract-address'; -export const POCO_ADDRESS = '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f'; -export const DATASET_REGISTRY_ADDRESS = '0x799DAa22654128d0C64d5b79eac9283008158730'; -export const APP_REGISTRY_ADDRESS = '0xB1C52075b276f87b1834919167312221d50c9D16'; +import config from './config.cjs'; + +export const POCO_ADDRESS = config.POCO_ADDRESS; +export const DATASET_REGISTRY_ADDRESS = config.DATASET_REGISTRY_ADDRESS; +export const APP_REGISTRY_ADDRESS = config.APP_REGISTRY_ADDRESS; diff --git a/packages/sharing-smart-contract/config/env.cjs b/packages/sharing-smart-contract/config/env.cjs new file mode 100644 index 000000000..6d754368a --- /dev/null +++ b/packages/sharing-smart-contract/config/env.cjs @@ -0,0 +1,51 @@ +// Hardhat Ignition does not support ESM modules, so we use CommonJS syntax. +// TODO refactor this to use ESM syntax when Hardhat Ignition supports it. + +require('dotenv/config.js'); +const { z } = require('zod'); + +const addressRegex = /(^|\b)(0x)?[0-9a-fA-F]{40}(\b|$)/; +const privateKeyRegex = /(^|\b)(0x)?[0-9a-fA-F]{64}(\b|$)/; + +const envSchema = z.object({ + // Private key of the wallet used for transactions + PRIVATE_KEY: z + .string() + .regex(privateKeyRegex, 'Invalid private key format') + .optional() + .or(z.literal('')), + + // environment to use for configuration (prod/staging) + ENV: z.enum(['prod', 'staging'], 'ENV must be either "prod" or "staging"').default('prod'), + + // Address of the PoCo contract + POCO_ADDRESS: z + .string() + .regex(addressRegex, 'Invalid Ethereum address format') + .optional() + .or(z.literal('')), + + // Address of the DatasetRegistry + DATASET_REGISTRY_ADDRESS: z + .string() + .regex(addressRegex, 'Invalid Ethereum address format') + .optional() + .or(z.literal('')), + + // URL of the RPC used for network connection + RPC_URL: z.string().url('RPC_URL must be a valid URL').optional().or(z.literal('')), + + // Mnemonic for deployment or network interaction + MNEMONIC: z.string().min(1, 'MNEMONIC cannot be empty').optional().or(z.literal('')), + + FUJI_RPC_URL: z.string().url('FUJI_RPC_URL must be a valid URL').optional(), + + ARBITRUM_SEPOLIA_RPC_URL: z + .string() + .url('ARBITRUM_SEPOLIA_RPC_URL must be a valid URL') + .optional(), +}); + +module.exports = { + env: envSchema.parse(process.env), +}; diff --git a/packages/sharing-smart-contract/config/env.js b/packages/sharing-smart-contract/config/env.js index 3ede68f29..a95a9d745 100644 --- a/packages/sharing-smart-contract/config/env.js +++ b/packages/sharing-smart-contract/config/env.js @@ -1,39 +1,3 @@ -import 'dotenv/config.js'; -import { z } from 'zod'; +import _env from './env.cjs'; -const addressRegex = /(^|\b)(0x)?[0-9a-fA-F]{64}(\b|$)/; -const privateKeyRegex = /(^|\b)(0x)?[0-9a-fA-F]{64}(\b|$)/; - -const envSchema = z.object({ - // Private key of the wallet used for transactions - WALLET_PRIVATE_KEY: z - .string() - .regex(privateKeyRegex, 'Invalid private key format') - .optional() - .or(z.literal('')), - - // environment to use for configuration (prod/staging) - ENV: z.enum(['prod', 'staging'], 'ENV must be either "prod" or "staging"').default('prod'), - - // Address of the PoCo contract - POCO_ADDRESS: z - .string() - .regex(addressRegex, 'Invalid Ethereum address format') - .optional() - .or(z.literal('')), - - // Address of the DatasetRegistry - DATASET_REGISTRY_ADDRESS: z - .string() - .regex(addressRegex, 'Invalid Ethereum address format') - .optional() - .or(z.literal('')), - - // URL of the RPC used for network connection - RPC_URL: z.string().url('RPC_URL must be a valid URL').optional().or(z.literal('')), - - // Mnemonic for deployment or network interaction - MNEMONIC: z.string().min(1, 'MNEMONIC cannot be empty').optional().or(z.literal('')), -}); - -export const env = envSchema.parse(process.env); +export const env = _env.env; diff --git a/packages/sharing-smart-contract/hardhat.config.cjs b/packages/sharing-smart-contract/hardhat.config.cjs index d89e22a48..4843a7146 100644 --- a/packages/sharing-smart-contract/hardhat.config.cjs +++ b/packages/sharing-smart-contract/hardhat.config.cjs @@ -3,9 +3,10 @@ require('@nomicfoundation/hardhat-toolbox'); require('@openzeppelin/hardhat-upgrades'); require('hardhat-contract-sizer'); require('@openzeppelin/hardhat-upgrades'); -require('dotenv').config(); +require('hardhat-dependency-compiler'); +const env = require('./config/env.cjs'); -const { WALLET_PRIVATE_KEY } = process.env; +// TODO format const bellecourBase = { gasPrice: 0, @@ -34,14 +35,32 @@ module.exports = { bellecour: { ...bellecourBase, url: 'https://bellecour.iex.ec', - accounts: WALLET_PRIVATE_KEY ? [WALLET_PRIVATE_KEY] : [], + accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [], + }, + avalancheFujiTestnet: { + chainId: 43113, + url: env.FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', + accounts: [ + env.PRIVATE_KEY || + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + blockGasLimit: 8_000_000, + }, + arbitrumSepolia: { + chainId: 421614, + url: env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', + accounts: [ + process.env.PRIVATE_KEY || + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + blockGasLimit: 30_000_000, }, // poco-chain native config 'dev-native': { chainId: 65535, - url: process.env.RPC_URL ?? 'http://localhost:8545', + url: env.RPC_URL ?? 'http://localhost:8545', accounts: { - mnemonic: process.env.MNEMONIC ?? '', + mnemonic: env.MNEMONIC ?? '', }, gasPrice: 0, }, @@ -87,4 +106,16 @@ module.exports = { }, }, }, + ignition: { + strategyConfig: { + create2: { + salt: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + }, + dependencyCompiler: { + paths: [ + '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol', + ], + }, }; diff --git a/packages/sharing-smart-contract/ignition/modules/DataProtectorSharingModule.cts b/packages/sharing-smart-contract/ignition/modules/DataProtectorSharingModule.cts new file mode 100644 index 000000000..5a05fe1d6 --- /dev/null +++ b/packages/sharing-smart-contract/ignition/modules/DataProtectorSharingModule.cts @@ -0,0 +1,59 @@ +const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules'); + +const { + DATASET_REGISTRY_ADDRESS: defaultDatasetRegistryAddress, + POCO_ADDRESS: defaultPocoAddress, +} = require('../../config/config.cjs'); +const { env } = require('../../config/env.cjs'); + +// Hardhat Ignition does not support ESM yet. + +// @ts-ignore +module.exports = buildModule('DataProtectorSharingModule', (m) => { + const proxyAdminOwner = m.getAccount(0); + const pocoAddress = env.POCO_ADDRESS || defaultPocoAddress; + const datasetRegistryAddress = env.DATASET_REGISTRY_ADDRESS || defaultDatasetRegistryAddress; + + // Whitelist + const addOnlyAppWhitelistRegistryImpl = m.contract('AddOnlyAppWhitelistRegistry', [], { + id: 'AddOnlyAppWhitelistRegistryImpl', + }); + const addOnlyAppWhitelistRegistryProxy = m.contract( + 'TransparentUpgradeableProxy', + [ + addOnlyAppWhitelistRegistryImpl, + proxyAdminOwner, + '0x', // No initialization data. + ], + { + id: 'AddOnlyAppWhitelistRegistryProxy', + }, + ); + const addOnlyAppWhitelistRegistry = m.contractAt( + 'AddOnlyAppWhitelistRegistry', + addOnlyAppWhitelistRegistryProxy, + ); + + // DPS + const dataProtectorSharingImpl = m.contract( + 'DataProtectorSharing', + [pocoAddress, datasetRegistryAddress, addOnlyAppWhitelistRegistryProxy], + { + id: 'DataProtectorSharingImpl', + }, + ); + const dataProtectorSharingProxy = m.contract( + 'TransparentUpgradeableProxy', + [ + dataProtectorSharingImpl, + proxyAdminOwner, + '0x', // No initialization data. + ], + { + id: 'DataProtectorSharingProxy', + }, + ); + const dataProtectorSharing = m.contractAt('DataProtectorSharing', dataProtectorSharingProxy); + + return { addOnlyAppWhitelistRegistry, dataProtectorSharing }; +}); diff --git a/packages/sharing-smart-contract/package-lock.json b/packages/sharing-smart-contract/package-lock.json index e42065b35..0480243e7 100644 --- a/packages/sharing-smart-contract/package-lock.json +++ b/packages/sharing-smart-contract/package-lock.json @@ -26,6 +26,7 @@ "eslint-plugin-import": "^2.31.0", "hardhat": "^2.23.0", "hardhat-contract-sizer": "^2.10.0", + "hardhat-dependency-compiler": "^1.2.1", "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-solidity": "^1.4.1", @@ -8618,6 +8619,19 @@ "hardhat": "^2.0.0" } }, + "node_modules/hardhat-dependency-compiler": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/hardhat-dependency-compiler/-/hardhat-dependency-compiler-1.2.1.tgz", + "integrity": "sha512-xG5iwbspTtxOEiP5UsPngEYQ1Hg+fjTjliapIjdTQmwGkCPofrsDhQDV2O/dopcYzcR68nTx2X8xTewYHgA2rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14.0" + }, + "peerDependencies": { + "hardhat": "^2.0.0" + } + }, "node_modules/hardhat-gas-reporter": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz", diff --git a/packages/sharing-smart-contract/package.json b/packages/sharing-smart-contract/package.json index 546c88191..e5a875c6e 100644 --- a/packages/sharing-smart-contract/package.json +++ b/packages/sharing-smart-contract/package.json @@ -9,7 +9,7 @@ "clean": "hardhat clean", "compile": "hardhat clean && hardhat compile && npm run artifact-to-abis", "verify": "hardhat verify", - "deploy": "npm run compile && hardhat run ./scripts/deploy.js", + "deploy": "hardhat ignition deploy ignition/modules/DataProtectorSharingModule.cts --strategy create2", "update-env": "hardhat run ./scripts/updateEnv.js", "upgrade": "hardhat run ./scripts/upgrade.js", "upgrade-local-fork": "mkdir -p .openzeppelin/local-fork && cp -r .openzeppelin/prod/. .openzeppelin/local-fork && MANIFEST_DEFAULT_DIR=.openzeppelin/local-fork ENV=prod hardhat run ./scripts/upgrade-local-fork.js", @@ -41,6 +41,7 @@ "eslint-plugin-import": "^2.31.0", "hardhat": "^2.23.0", "hardhat-contract-sizer": "^2.10.0", + "hardhat-dependency-compiler": "^1.2.1", "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-solidity": "^1.4.1", diff --git a/packages/sharing-smart-contract/scripts/deploy.js b/packages/sharing-smart-contract/scripts/deploy.js deleted file mode 100644 index b5de0b793..000000000 --- a/packages/sharing-smart-contract/scripts/deploy.js +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable no-console */ -import hre from 'hardhat'; -import { - DATASET_REGISTRY_ADDRESS as defaultDatasetRegistryAddress, - POCO_ADDRESS as defaultPocoAddress, -} from '../config/config.js'; -import { env } from '../config/env.js'; -import { saveDeployment } from '../utils/utils.js'; - -const { ethers, upgrades } = hre; - -async function main() { - console.log('Starting deployment...'); - const [deployer] = await ethers.getSigners(); - console.log('Deploying contracts with the account:', deployer.address); - - const pocoAddress = env.POCO_ADDRESS || defaultPocoAddress; - const datasetRegistryAddress = env.DATASET_REGISTRY_ADDRESS || defaultDatasetRegistryAddress; - - console.log(`Using poco at ${pocoAddress}`); - console.log(`Using dataset registry at ${datasetRegistryAddress}`); - - const AddOnlyAppWhitelistRegistryFactory = await ethers.getContractFactory( - 'AddOnlyAppWhitelistRegistry', - ); - const addOnlyAppWhitelistRegistryContract = await upgrades.deployProxy( - AddOnlyAppWhitelistRegistryFactory, - { - kind: 'transparent', - }, - ); - await addOnlyAppWhitelistRegistryContract.waitForDeployment(); - const addOnlyAppWhitelistRegistryAddress = - await addOnlyAppWhitelistRegistryContract.getAddress(); - - const deployAddOnlyAppWhitelistRegistryTxReceipt = await addOnlyAppWhitelistRegistryContract - .deploymentTransaction() - .wait(); - - await saveDeployment('AddOnlyAppWhitelistRegistry')({ - address: addOnlyAppWhitelistRegistryAddress, - args: '', - block: deployAddOnlyAppWhitelistRegistryTxReceipt.blockNumber, - }); - - const DataProtectorSharingFactory = await ethers.getContractFactory('DataProtectorSharing'); - - const dataProtectorSharingConstructorArgs = [ - pocoAddress, - datasetRegistryAddress, - addOnlyAppWhitelistRegistryAddress, - ]; - const dataProtectorSharingContract = await upgrades.deployProxy(DataProtectorSharingFactory, { - kind: 'transparent', - constructorArgs: dataProtectorSharingConstructorArgs, - }); - await dataProtectorSharingContract.waitForDeployment(); - const proxyAddress = await dataProtectorSharingContract.getAddress(); - - const deployDataProtectorSharingTxReceipt = await dataProtectorSharingContract - .deploymentTransaction() - .wait(); - - await saveDeployment('DataProtectorSharing')({ - address: proxyAddress, - args: dataProtectorSharingConstructorArgs.join(' '), - block: deployDataProtectorSharingTxReceipt.blockNumber, - }); - - console.log(`Proxy AddOnlyAppWhitelistRegistry address: ${addOnlyAppWhitelistRegistryAddress}`); - console.log(`Proxy DataProtectorSharing address: ${proxyAddress}`); - - // Verify smart-contract - try { - await hre.run('verify:verify', { - address: addOnlyAppWhitelistRegistryAddress, - }); - } catch (e) { - console.log('Proxy verification for AppWhitelistRegistryContract may have failed :', e); - } - try { - await hre.run('verify:verify', { - address: proxyAddress, - constructorArguments: dataProtectorSharingConstructorArgs, - }); - } catch (e) { - console.log('Proxy verification for DataProtectorSharingContract may have failed :', e); - } -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/packages/sharing-smart-contract/tsconfig.json b/packages/sharing-smart-contract/tsconfig.json new file mode 100644 index 000000000..7793ef8df --- /dev/null +++ b/packages/sharing-smart-contract/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["ignition/modules"] +}