Skip to content

Commit

Permalink
Merge pull request #2 from aragon/f/unified-governance-plugin-setup
Browse files Browse the repository at this point in the history
Unified governance plugin setup
  • Loading branch information
brickpop authored Nov 9, 2023
2 parents e303806 + 833f1ae commit 0fbb499
Show file tree
Hide file tree
Showing 24 changed files with 767 additions and 1,056 deletions.
99 changes: 94 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,26 +422,115 @@ This is taken care by the `DAOFactory`. The DAO creator calls `daoFactory.create
- The call contains:
- The DAO settings
- An array with the details and the settings of the desired plugins
- The method will deploy a new DAO and set itself as ROOT
- It will then call `prepareInstallation()` on all plugins and `applyInstallation()` right away
- It will finally drop `ROOT_PERMISSION` on itself
- The method will deploy a new DAO and grant `ROOT_PERMISSION` to the `DaoFactory`, temporarily
- Given the settings of the desired plugins, it will call the `PluginSetupProcessor`
- The PSP will then call `prepareInstallation()` on the given plugin set up contract
- Immedially after, `applyInstallation()` will be called by the `DaoFactory`
- The DaoFactory drops `ROOT_PERMISSION` on itself

[See a JS example of installing plugins during a DAO's deployment](https://devs.aragon.org/docs/sdk/examples/client/create-dao#create-a-dao)

### Installing plugins afterwards

Plugin changes need a proposal to be passed when the DAO already exists.

1. Calling `pluginSetup.prepareInstallation()`
1. Calling `pluginSetupProcessor.prepareInstallation()` which will call `prepareInstallation()` on the plugin's setup contract
- A new plugin instance is deployed with the desired settings
- The call requests a set of permissions to be applied by the DAO
- The call returns a set of requested permissions to be applied by the DAO
2. Editors pass a proposal to make the DAO call `applyInstallation()` on the [PluginSetupProcessor](https://devs.aragon.org/docs/osx/how-it-works/framework/plugin-management/plugin-setup/)
- This applies the requested permissions and the plugin becomes installed

See `SpacePluginSetup`, `PersonalSpaceAdminPluginSetup`, `MemberAccessPluginSetup` and `MainVotingPluginSetup`.

[Learn more about plugin setup's](https://devs.aragon.org/docs/osx/how-it-works/framework/plugin-management/plugin-setup/) and [preparing installations](https://devs.aragon.org/docs/sdk/examples/client/prepare-installation).

### Plugin Setup install parameters

In both of the cases described above, a call to `prepareInstallation()` will be made by the `PluginSetupProcessor` from OSx.

```solidity
function prepareInstallation(
address _dao,
bytes memory _data
) external returns (address plugin, PreparedSetupData memory preparedSetupData)
```

- The first parameter (dao address) will be provided by the PSP.
- The second parameter contains an arbitrary array of bytes, with the ABI encoded custom settings that the plugin setup needs to operate.

Convenience functions are provided within the plugin setup contracts:

```solidity
// SpacePluginSetup.sol
function encodeInstallationParams(
string memory _firstBlockContentUri,
address _predecessorAddress,
address _pluginUpgrader
) public pure returns (bytes memory);
function encodeUninstallationParams(
address _pluginUpgrader
) public pure returns (bytes memory)
```

The JSON encoded ABI definition can also be found at the corresponding `<name>-build-metadata.json` file:

```json
{
// ...
"pluginSetup": {
"prepareInstallation": {
// ...
"inputs": [
{
"name": "firstBlockContentUri",
"type": "string",
"internalType": "string",
"description": "The inital contents of the first block item."
},
{
"internalType": "address",
"name": "predecessorAddress",
"type": "address"
},
{
"internalType": "address",
"name": "pluginUpgrader",
"type": "address"
}
]
},
```

The same also applies to `prepareUpdate` (if present) and to `prepareUninstallation`.

### Available setup contracts

#### GovernancePluginsSetup

This contracts implements the deployment script for:

- `MainVotingPlugin`
- `MemberAccessPlugin`

The second plugin needs to know the address of the first one, therefore the contract deploys them together.

##### Note

When preparing the installation, an `InstallationPrepared` event is emitted. Using Typechain with Ethers:

- `event.args.preparedSetupData.plugin` contains the address of the Main Voting plugin
- `event.args.preparedSetupData.helpers` contains an array with the address of the Member Access plugin

#### SpacePluginSetup

This contract implements the deployment script for the `SpacePlugin` contract.

#### PersonalSpaceAdminPluginSetup

This contract implements the deployment script for the `PersonalSpaceAdminPlugin` contract.

## Deploying a DAO

The recommended way to create a DAO is by using `@aragon/sdk-client`. It uses the `DAOFactory` under the hood and it reduces the amount of low level interactions with the protocol.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
"typescript": "5.0.4"
},
"scripts": {
"build": "cd ./packages/contracts && yarn build && cd ../contracts-ethers && yarn build && cd ../subgraph && yarn build",
"test": "cd ./packages/contracts && yarn test && cd ../subgraph && yarn test",
"clean": "cd ./packages/contracts && yarn clean && cd ../contracts-ethers && yarn clean && yarn clean && cd ../subgraph && yarn clean",
"build": "cd ./packages/contracts && yarn build && cd ../contracts-ethers && yarn build",
"test": "cd ./packages/contracts && yarn test",
"clean": "cd ./packages/contracts && yarn clean && cd ../contracts-ethers && yarn clean && yarn clean",
"prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"",
"prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\""
}
Expand Down
8 changes: 2 additions & 6 deletions packages/contracts/deploy/01_repo/10_create_repo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
MainVotingPluginSetupParams,
MemberAccessPluginSetupParams,
GovernancePluginsSetupParams,
PersonalSpaceAdminPluginSetupParams,
SpacePluginSetupParams,
} from "../../plugin-setup-params";
Expand All @@ -23,10 +22,7 @@ const func: DeployFunction = function (hre: HardhatRuntimeEnvironment) {
deployRepo(hre, PersonalSpaceAdminPluginSetupParams.PLUGIN_REPO_ENS_NAME)
)
.then(() =>
deployRepo(hre, MemberAccessPluginSetupParams.PLUGIN_REPO_ENS_NAME)
)
.then(() =>
deployRepo(hre, MainVotingPluginSetupParams.PLUGIN_REPO_ENS_NAME)
deployRepo(hre, GovernancePluginsSetupParams.PLUGIN_REPO_ENS_NAME)
);
};

Expand Down
25 changes: 6 additions & 19 deletions packages/contracts/deploy/02_setup/10_setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
MainVotingPluginSetupParams,
MemberAccessPluginSetupParams,
GovernancePluginsSetupParams,
PersonalSpaceAdminPluginSetupParams,
SpacePluginSetupParams,
} from "../../plugin-setup-params";
Expand All @@ -23,7 +22,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
log: true,
});

// Space
// Personal Space
console.log(
`\nDeploying ${PersonalSpaceAdminPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME}`,
);
Expand All @@ -37,23 +36,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
},
);

// Space
console.log(
`\nDeploying ${MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME}`,
);

await deploy(MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME, {
from: deployer,
args: [],
log: true,
});

// Space
// Governance
console.log(
`\nDeploying ${MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME}`,
`\nDeploying ${GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME}`,
);

await deploy(MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME, {
await deploy(GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME, {
from: deployer,
args: [],
log: true,
Expand All @@ -64,7 +52,6 @@ export default func;
func.tags = [
SpacePluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
PersonalSpaceAdminPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
"Deployment",
];
64 changes: 15 additions & 49 deletions packages/contracts/deploy/02_setup/11_setup_conclude.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {
MainVotingPluginSetupParams,
MemberAccessPluginSetupParams,
GovernancePluginsSetupParams,
PersonalSpaceAdminPluginSetupParams,
SpacePluginSetupParams,
} from "../../plugin-setup-params";
import {
GovernancePluginsSetup__factory,
MainVotingPlugin__factory,
MainVotingPluginSetup__factory,
MemberAccessPlugin__factory,
MemberAccessPluginSetup__factory,
PersonalSpaceAdminPlugin__factory,
PersonalSpaceAdminPluginSetup__factory,
SpacePlugin__factory,
Expand All @@ -21,8 +19,7 @@ import { setTimeout } from "timers/promises";
const func: DeployFunction = function (hre: HardhatRuntimeEnvironment) {
return concludeSpaceSetup(hre)
.then(() => concludePersonalSpaceVotingSetup(hre))
.then(() => concludeMemberAccessVotingSetup(hre))
.then(() => concludeMainVotingSetup(hre));
.then(() => concludeGovernanceSetup(hre));
};

async function concludeSpaceSetup(hre: HardhatRuntimeEnvironment) {
Expand Down Expand Up @@ -99,27 +96,31 @@ async function concludePersonalSpaceVotingSetup(
});
}

async function concludeMemberAccessVotingSetup(
async function concludeGovernanceSetup(
hre: HardhatRuntimeEnvironment,
) {
const { deployments, network } = hre;
const [deployer] = await hre.ethers.getSigners();

console.log(
`Concluding ${MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME} deployment.\n`,
`Concluding ${GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME} deployment.\n`,
);

const setupDeployment = await deployments.get(
MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
);
const setup = MemberAccessPluginSetup__factory.connect(
const setup = GovernancePluginsSetup__factory.connect(
setupDeployment.address,
deployer,
);
const implementation = MemberAccessPlugin__factory.connect(
const mainVotingPluginImplementation = MainVotingPlugin__factory.connect(
await setup.implementation(),
deployer,
);
const memberAccessPluginImplementation = MemberAccessPlugin__factory.connect(
await setup.memberAccessPluginImplementation(),
deployer,
);

// Add a timeout for polygon because the call to `implementation()` can fail for newly deployed contracts in the first few seconds
if (network.name === "polygon") {
Expand All @@ -132,45 +133,11 @@ async function concludeMemberAccessVotingSetup(
args: setupDeployment.args,
});
hre.aragonToVerifyContracts.push({
address: implementation.address,
address: mainVotingPluginImplementation.address,
args: [],
});
}

async function concludeMainVotingSetup(
hre: HardhatRuntimeEnvironment,
) {
const { deployments, network } = hre;
const [deployer] = await hre.ethers.getSigners();

console.log(
`Concluding ${MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME} deployment.\n`,
);

const setupDeployment = await deployments.get(
MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
);
const setup = MainVotingPluginSetup__factory.connect(
setupDeployment.address,
deployer,
);
const implementation = MainVotingPlugin__factory.connect(
await setup.implementation(),
deployer,
);

// Add a timeout for polygon because the call to `implementation()` can fail for newly deployed contracts in the first few seconds
if (network.name === "polygon") {
console.log(`Waiting 30secs for ${network.name} to finish up...`);
await setTimeout(30000);
}

hre.aragonToVerifyContracts.push({
address: setupDeployment.address,
args: setupDeployment.args,
});
hre.aragonToVerifyContracts.push({
address: implementation.address,
address: memberAccessPluginImplementation.address,
args: [],
});
}
Expand All @@ -179,7 +146,6 @@ export default func;
func.tags = [
SpacePluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
PersonalSpaceAdminPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
"Verification",
];
9 changes: 3 additions & 6 deletions packages/contracts/deploy/02_setup/12_publish.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
MainVotingPluginSetupParams,
MemberAccessPluginSetupParams,
GovernancePluginsSetupParams,
PersonalSpaceAdminPluginSetupParams,
PluginSetupParams,
SpacePluginSetupParams,
Expand All @@ -19,8 +18,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types";
const func: DeployFunction = function (hre: HardhatRuntimeEnvironment) {
return publishPlugin(hre, SpacePluginSetupParams)
.then(() => publishPlugin(hre, PersonalSpaceAdminPluginSetupParams))
.then(() => publishPlugin(hre, MemberAccessPluginSetupParams))
.then(() => publishPlugin(hre, MainVotingPluginSetupParams));
.then(() => publishPlugin(hre, GovernancePluginsSetupParams));
};

async function publishPlugin(
Expand Down Expand Up @@ -150,7 +148,6 @@ export default func;
func.tags = [
SpacePluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
PersonalSpaceAdminPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MemberAccessPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
MainVotingPluginSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
GovernancePluginsSetupParams.PLUGIN_SETUP_CONTRACT_NAME,
"Publication",
];
Loading

0 comments on commit 0fbb499

Please sign in to comment.