diff --git a/.env.example b/.env.example index c572777..439b5af 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,19 @@ # GENERAL -## The network used for testing purposes +## The network used NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "polygonMumbai"] -# PROTOCOL CONTRACTS +## DEPLOYMENT +DEPLOYMENT_RPC_ENDPOINT="https://host/path" +## Hex encoded private keys separated by a comma `,` +## The first item will be the deployment wallet +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +# OSx protocol contracts PLUGIN_REPO_FACTORY_ADDRESS="0xA69347F49dD618cb4577670D0728684AfAa01197" PLUGIN_SETUP_PROCESSOR_ADDRESS="0xAc7e4fB4a2f58b7EA5516f47b6f804956Faf0134" - -## Hex encoded private keys separated by a comma `,`a -PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Default hardhat account 0 private key. DON'T USE FOR DEPLOYMENTS +MANAGING_DAO_ADDRESS="0x617e4fB3a2358b7EA5a16f47b6f1f4956Faf0162" ## API keys INFURA_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" diff --git a/README.md b/README.md index aeb3507..246739e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ yarn build yarn test ``` -See [Deployment](#deployment) below +See the [deployment checklist](./README_DEPLOYMENT.md) ## Overview @@ -774,112 +774,3 @@ The plugins from this repo are built on top of many contract primitives from Ara - `minProposerVotingPower` wasn't used The rest of dependencies are imported directly from Aragon or from OpenZeppelin. - -## Deployment - -### On a blockchain supported by Aragon - -If the current contracts are meant to be deployed on a blockchain like Mainnet, Sepolia, Polygon, Base, etc, the only step needed is to define the relevant env vars on `.env` and run the following command: - -```sh -cd packages/contracts -npx hardhat deploy --network -``` - -### On a custom blockchain - -In the case of specific blockchains where OSx support is not officially available, you will need to deploy it first. The high level process looks like: - -0. Prepare a wallet for the whole deployment process -1. Deploy OSx - 1. This will create a Managing DAO, where the deployer wallet can `execute()` actions -2. Deploy the plugins of this project -3. Install the plugin that will control the Managing DAO -4. Revoke the `ROOT_PERMISSION` from the deployment wallet on the DAO - -#### 1) Deploy OSx - -Clone [https://github.com/aragon/osx](the OSx repo) into a separate folder. - -```sh -yarn -cd packages/contracts -yarn build -yarn test -``` - -- Edit the `packages/contracts/hardhat.config.ts` to include the details of your target network. -- Edit the `.env` file to make use of the deployment wallet's private key. - -```sh -npx hardhat deploy --network -``` - -Write down the address of the deployed contracts. The result should look similar to this: - -```json -{ - "managingDAOImplementation": "0x731b7F3d74C9dc25A90af73B960ad51f42481d6c", - "managingDAO": "0x1C57A251B1902656693f689aA69389f2a6f2a432", - "ENSRegistry": "0xE847017f1e18F7bF35b180fD45b4dAC18E81d568", - "PublicResolver": "0xE3B1288048f898A28a78FCf9942E14Cc853fFEF2", - "DAO_ENSSubdomainRegistrar_Implementation": "0xd92C33f309D6e795DCe1980aBc42D3431b0af0e7", - "DAO_ENSSubdomainRegistrar": "0xcf9D94Ddd248694B66D1D445b85ccbE385634Cc8", - "Plugin_ENSSubdomainRegistrar_Implementation": "0x7BC82fCba3521B15792423ac4E6076582235263B", - "Plugin_ENSSubdomainRegistrar": "0xd14C706586c6177d54D201df009b75FB14E8AB5E", - "DAORegistry_Implementation": "0x66a19CC345dAB31dfb6295017819d54dB594DE56", - "DAORegistry": "0x11d3B1B24C19B5672b92CD535d2F1F35C53AC543", - "PluginRepoRegistry_Implementation": "0x38b112318cfd563Fa5de538E7c219bf72F1CcA6a", - "PluginRepoRegistry": "0x9b51505f7bf3A45BC92F6bE269324096abEC0A73", - "PluginRepoFactory": "0xA69347F49dD615cb4577670D0728684AfAa01197", - "PluginSetupProcessor": "0xAc7e4fB4a2158b7EA5516f47b6f804956Faf0134", - "DAOFactory": "0x2d11E9413264e3814C2a21160cBCcb9Dc3C96890" -} -``` - -You are going to need the `PluginRepoFactory`, the `PluginSetupProcessor` and the `managingDAO`. - -#### 2) Deploy your plugins - -On this repository: - -- Edit the `packages/contracts/hardhat.config.ts` to include the details of your target network. -- Edit the `packages/contracts/plugin-setup-params.ts` to define the details of your new plugins - - Make sure that you don't deploy the same plugin repo twice, since this will cause an ENS collision - - In such case, either define a new ENS subdomain or consider running the step 1 again -- Edit the `.env` file - - Define the deployment wallet's private key - - Define the `PLUGIN_REPO_FACTORY_ADDRESS` and the `PLUGIN_SETUP_PROCESSOR_ADDRESS` from the contracts [deployed above](#1-deploy-osx) - -```sh -cd packages/contracts -yarn build -npx hardhat deploy --network -``` - -With this step, the deployment would be technically done. - -However, as the maintainer of the deployment, you are strongly encouraged to configure the Managing DAO such in a way that updates are approved by a group of people. - -#### 3) Install a governance plugin to the Managing DAO - -The Managing DAO is the contract that has the role of managing certain protocol modules, like the Plugin Registry, the DAO registry, etc. In case a new OSx version is available, only that DAO is be able to push upgrades and perform certain management tasks, which makes it a key component. - -The Managing DAO will be created with the following permissions: - -- The Managing DAO holds `ROOT_PERMISSION` on itself -- The deployer address holds `EXECUTE_PERMISSION` on the Managing DAO - -Among the plugins deployed [in the step above](#2-deploy-your-plugins), one of them should be installed to the Managing DAO. - -In your custom script make sure to: - -1. Use the same deployment wallet as before -2. Call `prepareInstallation()` on the Plugin Setup Processor deployed during the [Deploy OSX](#1-deploy-osx) step -3. Call `execute()` on the Managing DAO, with an action that calls `applyInstallation()` on the Plugin Setup Processor - -#### 4) Drop the ROOT permission of the deployer - -In the same custom script as before: - -1. Call `execute()` on the Managing DAO, with an action that calls `revoke(dao, deploymentWalletAddr, ROOT_PERMISSION_ID)` on the DAO itself diff --git a/README_DEPLOYMENT.md b/README_DEPLOYMENT.md new file mode 100644 index 0000000..32f8c98 --- /dev/null +++ b/README_DEPLOYMENT.md @@ -0,0 +1,108 @@ +# Geo Browser - Aragon OSx + +## Deployment + +### On a blockchain supported by Aragon + +If the current contracts are meant to be deployed on a blockchain like Mainnet, Sepolia, Polygon, Base, etc, the only step needed is to define the relevant env vars on `.env` and run the following command: + +```sh +cd packages/contracts +npx hardhat deploy --network +``` + +### On a custom blockchain + +In the case of specific blockchains where OSx support is not officially available, you will need to deploy it first. The high level process looks like: + +0. Prepare a wallet for the whole deployment process +1. Deploy OSx + 1. This will create a Managing DAO, where the deployer wallet can `execute()` actions +2. Deploy the plugins of this project +3. Install the plugin that will control the Managing DAO +4. Revoke the `ROOT_PERMISSION` from the deployment wallet on the DAO + +#### 1) Deploy OSx + +Clone [https://github.com/aragon/osx](the OSx repo) into a separate folder. + +```sh +yarn +git checkout simpler-deployment +cd packages/contracts +cp .env.example .env +nano .env # Update the values +yarn build +# yarn test (optional) +``` + +- Edit the `packages/contracts/hardhat.config.ts` to include the details of your target network. +- Edit the `.env` file to make use of the deployment wallet's private key. +- Open the `DEPLOYMENT_CHECKLIST.md` file and follow the instructions + +Write down the address of the deployed contracts. The result should look similar to this: + +```json +{ + "managingDAOImplementation": "0x731b7F3d74C9dc25A90af73B960ad51f42481d6c", + "managingDAO": "0x1C57A251B1902656693f689aA69389f2a6f2a432", + "ENSRegistry": "0xE847017f1e18F7bF35b180fD45b4dAC18E81d568", + "PublicResolver": "0xE3B1288048f898A28a78FCf9942E14Cc853fFEF2", + "DAO_ENSSubdomainRegistrar_Implementation": "0xd92C33f309D6e795DCe1980aBc42D3431b0af0e7", + "DAO_ENSSubdomainRegistrar": "0xcf9D94Ddd248694B66D1D445b85ccbE385634Cc8", + "Plugin_ENSSubdomainRegistrar_Implementation": "0x7BC82fCba3521B15792423ac4E6076582235263B", + "Plugin_ENSSubdomainRegistrar": "0xd14C706586c6177d54D201df009b75FB14E8AB5E", + "DAORegistry_Implementation": "0x66a19CC345dAB31dfb6295017819d54dB594DE56", + "DAORegistry": "0x11d3B1B24C19B5672b92CD535d2F1F35C53AC543", + "PluginRepoRegistry_Implementation": "0x38b112318cfd563Fa5de538E7c219bf72F1CcA6a", + "PluginRepoRegistry": "0x9b51505f7bf3A45BC92F6bE269324096abEC0A73", + "PluginRepoFactory": "0xA69347F49dD615cb4577670D0728684AfAa01197", + "PluginSetupProcessor": "0xAc7e4fB4a2158b7EA5516f47b6f804956Faf0134", + "DAOFactory": "0x2d11E9413264e3814C2a21160cBCcb9Dc3C96890" +} +``` + +Copy the `PluginRepoFactory`, the `PluginSetupProcessor` and the `managingDAO` addresses for the next step. + +#### 2) Run the plugin deployment script + +Back to this repository: + +- Update the `.env` file with the values that correspond to your target blockchain + - `NETWORK_NAME` and `DEPLOYMENT_RPC_ENDPOINT` + - Alternatively, edit the `packages/contracts/hardhat.config.ts` to manually customize the HardHat client + - Define the deployment wallet's `PRIVATE_KEY` + - Define the protocol addresses [you copied before](#1-deploy-osx): + - `PLUGIN_REPO_FACTORY_ADDRESS`, `PLUGIN_SETUP_PROCESSOR_ADDRESS` and `MANAGING_DAO_ADDRESS` +- Edit the `packages/contracts/plugin-setup-params.ts` to define the details of the plugins to depoy + - If you try to deploy the same plugin repo twice, you will encounter an ENS collision + - In such case, either define a new unique ENS subdomain or consider rerunning the step 1 and trying again + +```sh +cd packages/contracts +yarn build +npx hardhat deploy --network +``` + +In addition to deploying the plugins, the script will internally perform two more things: + +#### 3) Install a governance plugin to the Managing DAO + +The Managing DAO is the contract that has the role of managing certain protocol modules, like the Plugin Registry, the DAO registry, etc. In case a new OSx version is available, only that DAO is be able to push upgrades and perform certain management tasks, which makes it a key component. + +The Managing DAO will be created with the following permissions: + +- The Managing DAO holds `ROOT_PERMISSION` on itself +- The deployer address holds `EXECUTE_PERMISSION` on the Managing DAO + +Among the plugins deployed [in the step above](#2-deploy-your-plugins), one of them should be installed to the Managing DAO. + +The script takes care of: + +1. Use the same deployment wallet as before +2. Call `prepareInstallation()` on the Plugin Setup Processor deployed during the [Deploy OSX](#1-deploy-osx) step +3. Ask the Managing DAO to `execute()` an action that calls `applyInstallation()` of the new plugin + +#### 4) Revoke the EXECUTE permission granted to the deployer wallet + +The script calls `execute()` on the Managing DAO, with an action that calls `revoke(dao, deploymentWalletAddr, ROOT_PERMISSION_ID)` on the DAO itself diff --git a/packages/contracts/deploy/03_managing_dao/10_install_mgmt_dao_plugins.ts b/packages/contracts/deploy/03_managing_dao/10_install_mgmt_dao_plugins.ts new file mode 100644 index 0000000..1b02e7e --- /dev/null +++ b/packages/contracts/deploy/03_managing_dao/10_install_mgmt_dao_plugins.ts @@ -0,0 +1,140 @@ +import {GovernancePluginsSetupParams} from '../../plugin-setup-params'; +import { + GovernancePluginsSetup__factory, + MajorityVotingBase, + PluginSetupProcessor__factory, +} from '../../typechain'; +import { + InstallationPreparedEvent, + PluginSetupRefStruct, +} from '../../typechain/@aragon/osx/framework/plugin/setup/PluginSetupProcessor'; +import {findEvent, hashHelpers} from '../../utils/helpers'; +import {getPluginRepoInfo} from '../../utils/plugin-repo-info'; +import {IDAO, IDAO__factory, PluginSetupProcessor} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const MANAGING_DAO_ADDRESS = process.env.MANAGING_DAO_ADDRESS ?? ''; +const PLUGIN_SETUP_PROCESSOR_ADDRESS = + process.env.PLUGIN_SETUP_PROCESSOR_ADDRESS ?? ''; +const MGMT_DAO_PROPOSAL_DURATION = + parseInt(process.env.MGMT_DAO_PROPOSAL_DURATION ?? '604800') || + 60 * 60 * 24 * 7; +const MGMT_DAO_MIN_PROPOSAL_PARTICIPATION = + parseInt(process.env.MGMT_DAO_MIN_PROPOSAL_PARTICIPATION ?? '500000') || + 500_000; // 50% +const MGMT_DAO_PROPOSAL_SUPPORT_THRESHOLD = + parseInt(process.env.MGMT_DAO_PROPOSAL_SUPPORT_THRESHOLD ?? '500000') || + 500_000; // 50% + +// Main function +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, network} = hre; + const [deployer] = await hre.ethers.getSigners(); + + // Locate the plugin setup address + const setupDeployment = await deployments.get( + GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME + ); + const pluginSetup = GovernancePluginsSetup__factory.connect( + setupDeployment.address, + deployer + ); + + // Call prepare installation + const settings: MajorityVotingBase.VotingSettingsStruct = { + duration: MGMT_DAO_PROPOSAL_DURATION, + minParticipation: MGMT_DAO_MIN_PROPOSAL_PARTICIPATION, + supportThreshold: MGMT_DAO_PROPOSAL_SUPPORT_THRESHOLD, + votingMode: 1, // Early execution + }; + const initialEditors = [] as string[]; + const memberAccessProposalDuration = MGMT_DAO_PROPOSAL_DURATION * 3; // Time before expired + const pluginUpgrader = '0x0000000000000000000000000000000000000000'; // Only the DAO + const installData = await pluginSetup.encodeInstallationParams( + settings, + initialEditors, + memberAccessProposalDuration, + pluginUpgrader + ); + + if (!PLUGIN_SETUP_PROCESSOR_ADDRESS) + throw new Error('PLUGIN_SETUP_PROCESSOR_ADDRESS cannot be empty'); + + console.log('Preparing an installation of the standard governance plugin'); + const psp = PluginSetupProcessor__factory.connect( + PLUGIN_SETUP_PROCESSOR_ADDRESS, + deployer + ); + + const pluginRepoInfo = getPluginRepoInfo( + GovernancePluginsSetupParams.PLUGIN_REPO_ENS_NAME, + network.name + ); + if (!pluginRepoInfo) throw new Error('The plugin repo cannot be found'); + + const pluginSetupRef: PluginSetupRefStruct = { + pluginSetupRepo: pluginRepoInfo.address, + versionTag: GovernancePluginsSetupParams.VERSION, + }; + + const tx = await psp.prepareInstallation(MANAGING_DAO_ADDRESS, { + pluginSetupRef, + data: installData, + }); + await tx.wait(); + + const preparedEvent = await findEvent( + tx, + 'InstallationPrepared' + ); + if (!preparedEvent) { + throw new Error('Failed to get InstallationPrepared event'); + } + + const pluginAddress = preparedEvent.args.plugin; + const helpers = preparedEvent.args.preparedSetupData.helpers; + const permissions = preparedEvent.args.preparedSetupData.permissions; + + // Encode a call to execute() > applyInstallation + console.log('Installing the plugin to the managing DAO'); + const applyParams: PluginSetupProcessor.ApplyInstallationParamsStruct = { + plugin: pluginAddress, + helpersHash: hashHelpers(helpers), + permissions, + pluginSetupRef: { + pluginSetupRepo: pluginRepoInfo.address, + versionTag: GovernancePluginsSetupParams.VERSION, + }, + }; + + const actions: IDAO.ActionStruct[] = [ + { + to: PLUGIN_SETUP_PROCESSOR_ADDRESS, + value: 0, + data: PluginSetupProcessor__factory.createInterface().encodeFunctionData( + 'applyInstallation', + [MANAGING_DAO_ADDRESS, applyParams] + ), + }, + ]; + + const tx2 = await IDAO__factory.connect( + MANAGING_DAO_ADDRESS, + deployer + ).execute( + '0x0000000000000000000000000000000000000000000000000000000000000000', + actions, + 0 + ); + await tx2.wait(); + + hre.managingDao.address = MANAGING_DAO_ADDRESS; + hre.managingDao.governancePlugin = pluginAddress; +}; + +export default func; +func.tags = [ + GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME, + 'ManagingDAO', +]; diff --git a/packages/contracts/deploy/03_managing_dao/11_drop_deployer_permission.ts b/packages/contracts/deploy/03_managing_dao/11_drop_deployer_permission.ts new file mode 100644 index 0000000..f2ca834 --- /dev/null +++ b/packages/contracts/deploy/03_managing_dao/11_drop_deployer_permission.ts @@ -0,0 +1,38 @@ +import {DAO__factory, IDAO} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const MANAGING_DAO_ADDRESS = process.env.MANAGING_DAO_ADDRESS ?? ''; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const [deployer] = await hre.ethers.getSigners(); + + console.log('Revoking the execute permission to the deployment wallet'); + + // Revoke the permission to the deployer wallet + const mgmtDAO = DAO__factory.connect(MANAGING_DAO_ADDRESS, deployer); + + const actions: IDAO.ActionStruct[] = [ + { + to: MANAGING_DAO_ADDRESS, + value: 0, + data: DAO__factory.createInterface().encodeFunctionData('revoke', [ + MANAGING_DAO_ADDRESS, + deployer.address, + await mgmtDAO.EXECUTE_PERMISSION_ID(), + ]), + }, + ]; + + const tx = await DAO__factory.connect(MANAGING_DAO_ADDRESS, deployer).execute( + '0x0000000000000000000000000000000000000000000000000000000000000000', + actions, + 0 + ); + await tx.wait(); + + console.log('Execute permission revoked'); +}; + +export default func; +func.tags = ['ManagingDAO']; diff --git a/packages/contracts/deploy/99_verification/20_mgmt_dao_plugin.ts b/packages/contracts/deploy/99_verification/20_mgmt_dao_plugin.ts new file mode 100644 index 0000000..9382acb --- /dev/null +++ b/packages/contracts/deploy/99_verification/20_mgmt_dao_plugin.ts @@ -0,0 +1,33 @@ +import {DAO__factory} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const [deployer] = await hre.ethers.getSigners(); + + console.log('\nChecking the Managing DAO governance plugin'); + + if (!hre.managingDao.address) { + throw new Error('The Managing DAO is not ready'); + } else if (!hre.managingDao.governancePlugin) { + throw new Error('The governance plugin of the Managing DAO is not ready'); + } + const mgmtDAO = DAO__factory.connect(hre.managingDao.address, deployer); + + const perm = await mgmtDAO.hasPermission( + mgmtDAO.address, + hre.managingDao.governancePlugin, + await mgmtDAO.EXECUTE_PERMISSION_ID(), + '0x' + ); + if (!perm) { + throw new Error( + 'The governance plugin of the Managing DAO cannot execute proposals on it' + ); + } +}; + +export default func; + +func.tags = ['Verification']; +func.runAtTheEnd = true; diff --git a/packages/contracts/deploy/99_verification/21_mgmt_dao_root.ts b/packages/contracts/deploy/99_verification/21_mgmt_dao_root.ts new file mode 100644 index 0000000..bdec9f0 --- /dev/null +++ b/packages/contracts/deploy/99_verification/21_mgmt_dao_root.ts @@ -0,0 +1,31 @@ +import {DAO__factory} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const [deployer] = await hre.ethers.getSigners(); + + console.log('\nChecking the permissions of the deployment wallet'); + + if (!hre.managingDao.address) { + throw new Error('The Managing DAO is not ready'); + } + const mgmtDAO = DAO__factory.connect(hre.managingDao.address, deployer); + + const perm = await mgmtDAO.hasPermission( + mgmtDAO.address, + deployer.address, + await mgmtDAO.EXECUTE_PERMISSION_ID(), + '0x' + ); + if (perm) { + throw new Error( + 'The governance plugin of the Managing DAO cannot execute proposals on it' + ); + } +}; + +export default func; + +func.tags = ['Verification']; +func.runAtTheEnd = true; diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 18311a2..e04175d 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -26,6 +26,7 @@ const apiUrls: NetworkNameMapping = { polygon: 'https://polygon-mainnet.infura.io/v3/', polygonMumbai: 'https://polygon-mumbai.infura.io/v3/', baseGoerli: 'https://goerli.base.org', + custom: process.env.DEPLOYMENT_RPC_ENDPOINT ?? '', }; export const networks: {[index: string]: NetworkUserConfig} = { @@ -58,6 +59,11 @@ export const networks: {[index: string]: NetworkUserConfig} = { url: `${apiUrls.baseGoerli}`, gasPrice: 20000000000, }, + custom: { + chainId: 100000, + url: apiUrls.custom, + // gasPrice: 20000000000, + }, }; // Uses hardhats private key if none is set. DON'T USE THIS ACCOUNT FOR DEPLOYMENTS @@ -79,6 +85,10 @@ for (const network in networks) { // Extend HardhatRuntimeEnvironment extendEnvironment((hre: HardhatRuntimeEnvironment) => { hre.aragonToVerifyContracts = []; + hre.managingDao = { + address: '', + governancePlugin: '', + }; }); const config: HardhatUserConfig = { diff --git a/packages/contracts/utils/hardhat.d.ts b/packages/contracts/utils/hardhat.d.ts index a6071bc..c53c0cd 100644 --- a/packages/contracts/utils/hardhat.d.ts +++ b/packages/contracts/utils/hardhat.d.ts @@ -6,5 +6,9 @@ export type VerifyEntry = { declare module 'hardhat/types' { interface HardhatRuntimeEnvironment { aragonToVerifyContracts: VerifyEntry[]; + managingDao: { + address: string; + governancePlugin: string; + }; } }