diff --git a/packages/contracts/src/SpacePluginSetup.sol b/packages/contracts/src/SpacePluginSetup.sol index 2a92e08..c95a3e5 100644 --- a/packages/contracts/src/SpacePluginSetup.sol +++ b/packages/contracts/src/SpacePluginSetup.sol @@ -79,7 +79,7 @@ contract SpacePluginSetup is PluginSetup { _targetPluginAddresses ); permissions[2] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.Grant, + operation: PermissionLib.Operation.GrantWithCondition, where: _dao, who: _pluginUpgrader, condition: address(_onlyPluginUpgraderCondition), diff --git a/packages/contracts/test/integration-testing/governance-plugins-setup.ts b/packages/contracts/test/integration-testing/governance-plugins-setup.ts index 356b762..6ed77c4 100644 --- a/packages/contracts/test/integration-testing/governance-plugins-setup.ts +++ b/packages/contracts/test/integration-testing/governance-plugins-setup.ts @@ -14,10 +14,7 @@ import { UpgradedEvent, } from '../../typechain/@aragon/osx/core/dao/DAO'; import {PluginSetupRefStruct} from '../../typechain/@aragon/osx/framework/dao/DAOFactory'; -import { - UpdateAppliedEvent, - UpdatePreparedEvent, -} from '../../typechain/@aragon/osx/framework/plugin/setup/PluginSetupProcessor'; +import {UpdatePreparedEvent} from '../../typechain/@aragon/osx/framework/plugin/setup/PluginSetupProcessor'; import { findEvent, findEventTopicLog, diff --git a/packages/contracts/test/integration-testing/space-setup.ts b/packages/contracts/test/integration-testing/space-setup.ts index fd36f9f..87ee68f 100644 --- a/packages/contracts/test/integration-testing/space-setup.ts +++ b/packages/contracts/test/integration-testing/space-setup.ts @@ -6,34 +6,58 @@ import { SpacePluginSetup, SpacePluginSetup__factory, } from '../../typechain'; +import { + ExecutedEvent, + UpgradedEvent, +} from '../../typechain/@aragon/osx/core/dao/DAO'; import {PluginSetupRefStruct} from '../../typechain/@aragon/osx/framework/dao/DAOFactory'; -import {osxContracts} from '../../utils/helpers'; +import {UpdatePreparedEvent} from '../../typechain/@aragon/osx/framework/plugin/setup/PluginSetupProcessor'; +import { + findEvent, + findEventTopicLog, + getPluginRepoFactoryAddress, + hashHelpers, + osxContracts, +} from '../../utils/helpers'; import {toHex} from '../../utils/ipfs'; import {getPluginRepoInfo} from '../../utils/plugin-repo-info'; import {installPlugin, uninstallPlugin} from '../helpers/setup'; import {deployTestDao} from '../helpers/test-dao'; -import {ADDRESS_ZERO} from '../unit-testing/common'; +import { + ADDRESS_ONE, + ADDRESS_ZERO, + UPGRADE_PLUGIN_PERMISSION_ID, + ZERO_BYTES32, +} from '../unit-testing/common'; // import { getNamedTypesFromMetadata } from "../helpers/types"; import { DAO, + DAO__factory, PluginRepo__factory, + PluginRepoFactory__factory, + PluginRepoRegistry__factory, PluginSetupProcessor, PluginSetupProcessor__factory, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {BigNumber} from 'ethers'; -import {ethers} from 'hardhat'; +import {ethers, network} from 'hardhat'; + +const release = 1; +const hardhatForkNetwork = process.env.NETWORK_NAME ?? 'mainnet'; +const daoInterface = DAO__factory.createInterface(); +const pspInterface = PluginSetupProcessor__factory.createInterface(); describe('SpacePluginSetup processing', function () { - let alice: SignerWithAddress; + let deployer: SignerWithAddress; let psp: PluginSetupProcessor; let dao: DAO; let pluginRepo: PluginRepo; before(async () => { - [alice] = await ethers.getSigners(); + [deployer] = await ethers.getSigners(); const hardhatForkNetwork = process.env.NETWORK_NAME ?? 'mainnet'; @@ -48,11 +72,11 @@ describe('SpacePluginSetup processing', function () { // PSP psp = PluginSetupProcessor__factory.connect( osxContracts[hardhatForkNetwork]['PluginSetupProcessor'], - alice + deployer ); // Deploy DAO. - dao = await deployTestDao(alice); + dao = await deployTestDao(deployer); await dao.grant( dao.address, @@ -61,21 +85,21 @@ describe('SpacePluginSetup processing', function () { ); await dao.grant( psp.address, - alice.address, + deployer.address, ethers.utils.id('APPLY_INSTALLATION_PERMISSION') ); await dao.grant( psp.address, - alice.address, + deployer.address, ethers.utils.id('APPLY_UNINSTALLATION_PERMISSION') ); await dao.grant( psp.address, - alice.address, + deployer.address, ethers.utils.id('APPLY_UPDATE_PERMISSION') ); - pluginRepo = PluginRepo__factory.connect(pluginRepoInfo.address, alice); + pluginRepo = PluginRepo__factory.connect(pluginRepoInfo.address, deployer); }); context('Build 1', async () => { @@ -90,7 +114,7 @@ describe('SpacePluginSetup processing', function () { // Deploy setups. setup = SpacePluginSetup__factory.connect( (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, - alice + deployer ); pluginSetupRef = { @@ -113,7 +137,7 @@ describe('SpacePluginSetup processing', function () { plugin = SpacePlugin__factory.connect( results.preparedEvent.args.plugin, - alice + deployer ); }); @@ -129,3 +153,284 @@ describe('SpacePluginSetup processing', function () { }); }); }); + +describe('SpacePluginSetup with pluginUpgrader', () => { + let deployer: SignerWithAddress; + let pluginUpgrader: SignerWithAddress; + let pSetupBuild1: SpacePluginSetup; + + let psp: PluginSetupProcessor; + let dao: DAO; + let pluginRepo: PluginRepo; + let spFactory: SpacePluginSetup__factory; + + before(async () => { + [deployer, pluginUpgrader] = await ethers.getSigners(); + + // PSP + psp = PluginSetupProcessor__factory.connect( + osxContracts[hardhatForkNetwork]['PluginSetupProcessor'], + deployer + ); + + // Deploy DAO. + dao = await deployTestDao(deployer); + + // The DAO is root on itself + await dao.grant( + dao.address, + dao.address, + ethers.utils.id('ROOT_PERMISSION') + ); + + // Get the PluginRepoFactory address + const pluginRepoFactoryAddr: string = getPluginRepoFactoryAddress( + network.name + ); + + const pluginRepoFactory = PluginRepoFactory__factory.connect( + pluginRepoFactoryAddr, + deployer + ); + + // Create a new PluginRepo + let tx = await pluginRepoFactory.createPluginRepo( + 'testing-space-plugin', + deployer.address + ); + const eventLog = await findEventTopicLog( + tx, + PluginRepoRegistry__factory.createInterface(), + 'PluginRepoRegistered' + ); + if (!eventLog) { + throw new Error('Failed to get PluginRepoRegistered event log'); + } + + pluginRepo = PluginRepo__factory.connect( + eventLog.args.pluginRepo, + deployer + ); + + // Deploy PluginSetup build 1 + spFactory = new SpacePluginSetup__factory().connect(deployer); + pSetupBuild1 = await spFactory.deploy(psp.address); + + // Publish build 1 + tx = await pluginRepo.createVersion( + 1, + pSetupBuild1.address, + toHex('build'), + toHex('release') + ); + }); + + it('Allows pluginUpgrader to execute psp.applyUpdate()', async () => { + const pluginSetupRef1: PluginSetupRefStruct = { + versionTag: { + release, + build: 1, + }, + pluginSetupRepo: pluginRepo.address, + }; + + // Temporary permissions for installing + await dao.grant( + dao.address, + psp.address, + ethers.utils.id('ROOT_PERMISSION') + ); + await dao.grant( + psp.address, + deployer.address, + ethers.utils.id('APPLY_INSTALLATION_PERMISSION') + ); + + // Install build 1 + const data1 = await pSetupBuild1.encodeInstallationParams( + toHex('ipfs://1234'), + ADDRESS_ZERO, + pluginUpgrader.address + ); + const installation1 = await installPlugin(psp, dao, pluginSetupRef1, data1); + + // Drop temp permissions + await dao.revoke( + dao.address, + psp.address, + ethers.utils.id('ROOT_PERMISSION') + ); + await dao.revoke( + psp.address, + deployer.address, + ethers.utils.id('APPLY_INSTALLATION_PERMISSION') + ); + + // Deployed plugin + const spacePlugin = SpacePlugin__factory.connect( + installation1.preparedEvent.args.plugin, + deployer + ); + + // Check implementations build 1 + expect(await spacePlugin.implementation()).to.be.eq( + await pSetupBuild1.implementation() + ); + + // Deploy PluginSetup build 2 (new instance, disregarding the lack of changes) + const pSetupBuild2 = await spFactory.deploy(psp.address); + + // Check + expect(await pSetupBuild1.implementation()).to.not.be.eq( + await pSetupBuild2.implementation(), + 'Builds 1-2 implementation should differ' + ); + + // Publish build 2 + let tx = await pluginRepo.createVersion( + 1, + pSetupBuild2.address, + toHex('build'), + toHex('release') + ); + await tx.wait(); + + // Upgrade to build 2 + tx = await psp.prepareUpdate(dao.address, { + currentVersionTag: { + release: release, + build: 1, + }, + newVersionTag: { + release: release, + build: 2, + }, + pluginSetupRepo: pluginRepo.address, + setupPayload: { + currentHelpers: [], + data: '0x', + plugin: spacePlugin.address, + }, + }); + const preparedEvent = await findEvent( + tx, + 'UpdatePrepared' + ); + if (!preparedEvent) { + throw new Error('Failed to get UpdatePrepared event'); + } + + // Should not allow to execute other than the expected 3 actions + { + await expect( + dao.execute(toHex('23412341234123412341234123412341'), [], 0) + ).to.be.reverted; + await expect( + dao + .connect(pluginUpgrader) + .execute(toHex('23412341234123412341234123412341'), [], 0) + ).to.be.reverted; + await expect( + dao + .connect(pluginUpgrader) + .execute( + toHex('23412341234123412341234123412341'), + [{to: dao.address, value: 0, data: '0x'}], + 0 + ) + ).to.be.reverted; + await expect( + dao.connect(pluginUpgrader).execute( + toHex('23412341234123412341234123412341'), + [ + { + to: spacePlugin.address, + value: 0, + data: SpacePlugin__factory.createInterface().encodeFunctionData( + 'removeSubspace', + [ADDRESS_ONE] + ), + }, + ], + 0 + ) + ).to.be.reverted; + } + + // Params + const applyUpdateParams: PluginSetupProcessor.ApplyUpdateParamsStruct = { + plugin: spacePlugin.address, + pluginSetupRef: { + pluginSetupRepo: pluginRepo.address, + versionTag: { + release, + build: 2, + }, + }, + initData: preparedEvent.args.initData, + permissions: preparedEvent.args.preparedSetupData.permissions, + helpersHash: hashHelpers(preparedEvent.args.preparedSetupData.helpers), + }; + + // Execute grant + applyUpdate + revoke + tx = await dao.connect(pluginUpgrader).execute( + ZERO_BYTES32, + [ + // Grant permission to the PSP + { + to: dao.address, + value: 0, + data: daoInterface.encodeFunctionData('grant', [ + spacePlugin.address, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID, + ]), + }, + // Execute psp.applyUpdate() from the DAO to the plugin + { + to: psp.address, + value: 0, + data: pspInterface.encodeFunctionData('applyUpdate', [ + dao.address, + applyUpdateParams, + ]), + }, + // Revoke permission to the PSP + { + to: dao.address, + value: 0, + data: daoInterface.encodeFunctionData('revoke', [ + spacePlugin.address, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID, + ]), + }, + ], + 0 + ); + + const receipt = await tx.wait(); + const executedEvent: ExecutedEvent | undefined = ( + receipt.events || [] + ).find(event => event.event === 'Executed') as any; + if (!executedEvent) { + throw new Error('Failed to get Executed event'); + } + + const upgradedEvent = await findEvent(tx, 'Upgraded'); + if (!upgradedEvent) { + throw new Error('Failed to get Upgraded event'); + } + + // Check implementations build 2 + expect(await spacePlugin.implementation()).to.not.be.eq( + await pSetupBuild1.implementation(), + "Implementation shouldn't be build 1" + ); + + expect(await spacePlugin.implementation()).to.be.eq( + await pSetupBuild2.implementation(), + 'Implementation should be build 2' + ); + }); +});