diff --git a/.env.example b/.env.example index 61b90352..f0a5e6aa 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # GENERAL ## The network used for testing purposes -NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "baseMainnet", "arbitrum"] +NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon"] ## To upload the metadata for deployed contracts PUB_PINATA_JWT= @@ -24,12 +24,22 @@ POLYGONSCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" BASESCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" ARBISCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" -# SUBGRAPH +## Deployment addresses +# Note that addresses will be also used for testing so ensure they are valid on the network you are running the forking tests on. -## The Graph credentials -GRAPH_KEY="zzzzzzzzzzzz" +# optional, address if not provided will get it from the latest deployment on the network or from the ens registrar +# defined in the framework if it supports it. In case it is not found will create a new one. +PLUGIN_REPO_ADDRESS=0x0000000000000000000000000000000000000000 +# not optional, if not provided will not be able to deploy the plugin or run the forking tests. +PLUGIN_REPO_FACTORY_ADDRESS=0x0000000000000000000000000000000000000000 +# optional, only needed when a latest versions of the plugin are going to be deploy on a new network. +PLACEHOLDER_SETUP=0x0000000000000000000000000000000000000000 +# not optional, if not provided will not be able to transfer the ownership of the plugin when deploying +# the plugin or running the forking tests. +MANAGEMENT_DAO_ADDRESS=0x0000000000000000000000000000000000000000 ## Subgraph +GRAPH_KEY="zzzzzzzzzzzz" SUBGRAPH_NAME="osx" SUBGRAPH_VERSION="v1.0.0" SUBGRAPH_NETWORK_NAME="mainnet" # ["mainnet", "sepolia", "polygon", "base", "arbitrum"] diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index 22a783ce..19161ebb 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -37,4 +37,6 @@ jobs: run: 'yarn coverage' env: NETWORK_NAME: ${{ vars.NETWORK_NAME }} + PLUGIN_REPO_FACTORY_ADDRESS: ${{ vars.PLUGIN_REPO_FACTORY_ADDRESS }} + MANAGEMENT_DAO_ADDRESS: ${{ vars.MANAGEMENT_DAO_ADDRESS }} ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} diff --git a/packages/contracts/deploy/10_create_repo/11_create_repo.ts b/packages/contracts/deploy/10_create_repo/11_create_repo.ts index caf42237..5993be82 100644 --- a/packages/contracts/deploy/10_create_repo/11_create_repo.ts +++ b/packages/contracts/deploy/10_create_repo/11_create_repo.ts @@ -1,22 +1,19 @@ -import {PLUGIN_REPO_ENS_SUBDOMAIN_NAME} from '../../plugin-settings'; +import { + PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + PLUGIN_REPO_PROXY_NAME, +} from '../../plugin-settings'; import { findPluginRepo, getProductionNetworkName, pluginEnsDomain, + getPluginRepoFactory, + frameworkSupportsENS, } from '../../utils/helpers'; -import { - getLatestNetworkDeployment, - getNetworkNameByAlias, -} from '@aragon/osx-commons-configs'; -import { - UnsupportedNetworkError, - findEventTopicLog, -} from '@aragon/osx-commons-sdk'; +import {findEventTopicLog} from '@aragon/osx-commons-sdk'; import { PluginRepoRegistryEvents, PluginRepoRegistry__factory, PluginRepo__factory, - PluginRepoFactory__factory, } from '@aragon/osx-ethers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -27,32 +24,21 @@ import path from 'path'; * @param {HardhatRuntimeEnvironment} hre */ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - console.log( - `Creating the '${pluginEnsDomain( - hre - )}' plugin repo through Aragon's 'PluginRepoFactory'...` - ); + console.log(`Creating plugin repo through Aragon's 'PluginRepoFactory'...`); const [deployer] = await hre.ethers.getSigners(); - // Get the Aragon `PluginRepoFactory` from the `osx-commons-configs` - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; - } - const pluginRepoFactory = PluginRepoFactory__factory.connect( - networkDeployments.PluginRepoFactory.address, - deployer - ); + // Get the Aragon `PluginRepoFactory` + const pluginRepoFactory = await getPluginRepoFactory(hre); + + // if the framework supports ENS, use the subdomain from the `./plugin-settings.ts` file + // otherwise, use an empty string + const supportsENS = await frameworkSupportsENS(pluginRepoFactory); + const subdomain = supportsENS ? PLUGIN_REPO_ENS_SUBDOMAIN_NAME : ''; // Create the `PluginRepo` through the Aragon `PluginRepoFactory` const tx = await pluginRepoFactory.createPluginRepo( - PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + subdomain, deployer.address ); @@ -69,8 +55,18 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { deployer ); + // Save the plugin repo deployment + await hre.deployments.save(PLUGIN_REPO_PROXY_NAME, { + abi: PluginRepo__factory.abi, + address: pluginRepo.address, + receipt: await tx.wait(), + transactionHash: tx.hash, + }); + console.log( - `PluginRepo '${pluginEnsDomain(hre)}' deployed at '${pluginRepo.address}'.` + `PluginRepo ${ + supportsENS ? 'with ens:' + pluginEnsDomain(hre) : 'without ens' + } deployed at '${pluginRepo.address}'.` ); hre.aragonToVerifyContracts.push({ @@ -90,11 +86,11 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { console.log(`\n🏗️ ${path.basename(__filename)}:`); // Check if the ens record exists already - const {pluginRepo, ensDomain} = await findPluginRepo(hre); + const {pluginRepo} = await findPluginRepo(hre); if (pluginRepo !== null) { console.log( - `ENS name '${ensDomain}' was claimed already at '${ + `Plugin Repo already deployed at '${ pluginRepo.address }' on network '${getProductionNetworkName(hre)}'. Skipping deployment...` ); @@ -106,7 +102,7 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { return true; } else { - console.log(`ENS name '${ensDomain}' is unclaimed. Deploying...`); + console.log('Deploying Plugin Repo'); return false; } diff --git a/packages/contracts/deploy/20_new_version/23_publish.ts b/packages/contracts/deploy/20_new_version/23_publish.ts index bb8599a2..3e35e241 100644 --- a/packages/contracts/deploy/20_new_version/23_publish.ts +++ b/packages/contracts/deploy/20_new_version/23_publish.ts @@ -12,8 +12,8 @@ import { impersonatedManagementDaoSigner, isLocal, pluginEnsDomain, + isValidAddress, } from '../../utils/helpers'; -import {getLatestContractAddress} from '../helpers'; import {PLUGIN_REPO_PERMISSIONS, uploadToPinata} from '@aragon/osx-commons-sdk'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {writeFile} from 'fs/promises'; @@ -146,13 +146,17 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { [] ) ) { - const placeholderSetup = getLatestContractAddress('PlaceholderSetup', hre); - if (placeholderSetup == '' && !isLocal(hre)) { - throw new Error( - 'Aborting. Placeholder setup not present in this network' - ); - } if (latestBuild == 0 && VERSION.build > 1) { + // We are publishing the first version as build > 1. + // So we need to publish placeholders first.. + const placeholderSetup = process.env.PLACEHOLDER_SETUP; + + if (!placeholderSetup || !isValidAddress(placeholderSetup)) { + throw new Error( + 'Aborting. Placeholder setup not defined in .env or is not a valid address (is not an address or is address zero)' + ); + } + for (let i = 0; i < VERSION.build - 1; i++) { console.log('Publishing placeholder', i + 1); await createVersion( @@ -166,6 +170,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } } + // create the new version await createVersion( pluginRepo, VERSION.release, diff --git a/packages/contracts/deploy/30_upgrade_repo/_common.ts b/packages/contracts/deploy/30_upgrade_repo/_common.ts index 1070dcec..88fac792 100644 --- a/packages/contracts/deploy/30_upgrade_repo/_common.ts +++ b/packages/contracts/deploy/30_upgrade_repo/_common.ts @@ -1,8 +1,9 @@ -import {findPluginRepo, getProductionNetworkName} from '../../utils/helpers'; import { - getLatestNetworkDeployment, - getNetworkNameByAlias, -} from '@aragon/osx-commons-configs'; + findPluginRepo, + getProductionNetworkName, + getPluginRepoFactory, +} from '../../utils/helpers'; +import {getNetworkNameByAlias} from '@aragon/osx-commons-configs'; import {UnsupportedNetworkError} from '@aragon/osx-commons-sdk'; import {PluginRepo, PluginRepo__factory} from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -30,10 +31,6 @@ export async function fetchData( if (network === null) { throw new UnsupportedNetworkError(productionNetworkName); } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; - } // Get PluginRepo const {pluginRepo, ensDomain} = await findPluginRepo(hre); @@ -46,8 +43,10 @@ export async function fetchData( ); // Get the latest `PluginRepo` implementation as the upgrade target + const pluginRepoFactory = await getPluginRepoFactory(hre); + const latestPluginRepoImplementation = PluginRepo__factory.connect( - networkDeployments.PluginRepoBase.address, + await pluginRepoFactory.pluginRepoBase(), deployer ); diff --git a/packages/contracts/deploy/helpers.ts b/packages/contracts/deploy/helpers.ts deleted file mode 100644 index 04713a76..00000000 --- a/packages/contracts/deploy/helpers.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {isLocal} from '../utils/helpers'; -import { - getLatestNetworkDeployment, - getNetworkByNameOrAlias, -} from '@aragon/osx-commons-configs'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; - -export function getLatestContractAddress( - contractName: string, - hre: HardhatRuntimeEnvironment -): string { - const networkName = hre.network.name; - - const osxNetworkName = getNetworkByNameOrAlias(networkName); - if (!osxNetworkName) { - if (isLocal(hre)) { - return ''; - } - throw new Error(`Failed to find network ${networkName}`); - } - - const latestNetworkDeployment = getLatestNetworkDeployment( - osxNetworkName.name - ); - if (latestNetworkDeployment && contractName in latestNetworkDeployment) { - // safe cast due to conditional above, but we return the fallback string anyhow - const key = contractName as keyof typeof latestNetworkDeployment; - return latestNetworkDeployment[key]?.address ?? ''; - } - return ''; -} diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index f9eaf335..7992b09b 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -4,6 +4,7 @@ import {isZkSync} from './utils/zkSync'; import {VersionTag} from '@aragon/osx-commons-sdk'; import hre from 'hardhat'; +export const PLUGIN_REPO_PROXY_NAME = 'AdminRepoProxy'; export const PLUGIN_CONTRACT_NAME = 'Admin'; export const PLUGIN_SETUP_CONTRACT_NAME = isZkSync(hre.network.name) ? 'AdminSetupZkSync' diff --git a/packages/contracts/utils/helpers.ts b/packages/contracts/utils/helpers.ts index 83d5306a..1f2800ab 100644 --- a/packages/contracts/utils/helpers.ts +++ b/packages/contracts/utils/helpers.ts @@ -1,7 +1,9 @@ -import {PLUGIN_REPO_ENS_SUBDOMAIN_NAME} from '../plugin-settings'; +import { + PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + PLUGIN_REPO_PROXY_NAME, +} from '../plugin-settings'; import { SupportedNetworks, - getLatestNetworkDeployment, getNetworkNameByAlias, getPluginEnsDomain, } from '@aragon/osx-commons-configs'; @@ -10,6 +12,9 @@ import { DAO, DAO__factory, ENSSubdomainRegistrar__factory, + PluginRepoFactory, + PluginRepoFactory__factory, + PluginRepoRegistry__factory, ENS__factory, IAddrResolver__factory, PluginRepo, @@ -65,29 +70,68 @@ export function pluginEnsDomain(hre: HardhatRuntimeEnvironment): string { return `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}.${pluginEnsDomain}`; } +/** + * try to get the plugin repo first + * 1- env var PLUGIN_REPO_ADDRESS + * 2- try to get the latest network deployment + * 3- from the ens defined in the framework + * - plugin repo factory address from env var + */ export async function findPluginRepo( hre: HardhatRuntimeEnvironment ): Promise<{pluginRepo: PluginRepo | null; ensDomain: string}> { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName: string = getProductionNetworkName(hre); + const ensDomain = pluginEnsDomain(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); + // from env var + if (process.env.PLUGIN_REPO_ADDRESS) { + if (!isValidAddress(process.env.PLUGIN_REPO_ADDRESS)) { + throw new Error( + 'Plugin Repo in .env is not a valid address (is not an address or is address zero)' + ); + } + + return { + pluginRepo: PluginRepo__factory.connect( + process.env.PLUGIN_REPO_ADDRESS, + deployer + ), + ensDomain, + }; + } + + // from deployments + const pluginRepo = await hre.deployments.getOrNull(PLUGIN_REPO_PROXY_NAME); + if (pluginRepo) { + return { + pluginRepo: PluginRepo__factory.connect(pluginRepo.address, deployer), + ensDomain, + }; } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + // get ENS registrar from the plugin factory provided + const pluginRepoFactory = await getPluginRepoFactory(hre); + + const pluginRepoRegistry = PluginRepoRegistry__factory.connect( + await pluginRepoFactory.pluginRepoRegistry(), + deployer + ); + + const subdomainRegistrarAddress = + await pluginRepoRegistry.subdomainRegistrar(); + + if (subdomainRegistrarAddress === ethers.constants.AddressZero) { + // the network does not support ENS and the plugin repo could not be found by env var or deployments + return {pluginRepo: null, ensDomain: ''}; } const registrar = ENSSubdomainRegistrar__factory.connect( - networkDeployments.PluginENSSubdomainRegistrarProxy.address, + subdomainRegistrarAddress, deployer ); // Check if the ens record exists already const ens = ENS__factory.connect(await registrar.ens(), deployer); - const ensDomain = pluginEnsDomain(hre); const node = ethers.utils.namehash(ensDomain); const recordExists = await ens.recordExists(node); @@ -114,40 +158,34 @@ export async function getManagementDao( hre: HardhatRuntimeEnvironment ): Promise { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + const managementDaoAddress = process.env.MANAGEMENT_DAO_ADDRESS; + + // getting the management DAO from the env var + if (!managementDaoAddress || !isValidAddress(managementDaoAddress)) { + throw new Error( + 'Management DAO address in .env is not defined or is not a valid address (is not an address or is address zero)' + ); } - return DAO__factory.connect( - networkDeployments.ManagementDAOProxy.address, - deployer - ); + return DAO__factory.connect(managementDaoAddress, deployer); } -export async function getManagementDaoMultisig( +export async function getPluginRepoFactory( hre: HardhatRuntimeEnvironment -): Promise { +): Promise { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + const pluginRepoFactoryAddress = process.env.PLUGIN_REPO_FACTORY_ADDRESS; + + // from env var + if (!pluginRepoFactoryAddress || !isValidAddress(pluginRepoFactoryAddress)) { + throw new Error( + 'Plugin Repo Factory address in .env is not defined or is not a valid address (is not an address or is address zero)' + ); } - return DAO__factory.connect( - networkDeployments.ManagementDAOProxy.address, - deployer - ); + return PluginRepoFactory__factory.connect(pluginRepoFactoryAddress, deployer); } export async function impersonatedManagementDaoSigner( @@ -244,5 +282,25 @@ export async function createVersion( return tx; } +export function isValidAddress(address: string): boolean { + // check if the address is valid and not zero address + return ( + ethers.utils.isAddress(address) && address !== ethers.constants.AddressZero + ); +} + +export async function frameworkSupportsENS( + pluginRepoFactory: PluginRepoFactory +): Promise { + const [deployer] = await ethers.getSigners(); + const pluginRepoRegistry = PluginRepoRegistry__factory.connect( + await pluginRepoFactory.pluginRepoRegistry(), + deployer + ); + const subdomainRegistrar = await pluginRepoRegistry.subdomainRegistrar(); + + return subdomainRegistrar !== ethers.constants.AddressZero; +} + export const AragonOSxAsciiArt = " ____ _____ \n /\\ / __ \\ / ____| \n / \\ _ __ __ _ __ _ ___ _ __ | | | | (_____ __ \n / /\\ \\ | '__/ _` |/ _` |/ _ \\| '_ \\ | | | |\\___ \\ \\/ / \n / ____ \\| | | (_| | (_| | (_) | | | | | |__| |____) > < \n /_/ \\_\\_| \\__,_|\\__, |\\___/|_| |_| \\____/|_____/_/\\_\\ \n __/ | \n |___/ \n";