From 50085f298bd342f67f130e63d3490ad6b758103a Mon Sep 17 00:00:00 2001 From: Michael Heuer Date: Fri, 1 Mar 2024 14:57:52 +0100 Subject: [PATCH] test: adapted tests --- .../20_new_version/22_setup_conclude.ts | 9 +- packages/contracts/hardhat.config.ts | 2 +- .../test/10_unit-testing/11_plugin.ts | 143 ++++++++------ .../test/10_unit-testing/12_plugin-setup.ts | 89 +++++---- .../22_setup-processing.ts | 27 ++- .../20_integration-testing/test-helpers.ts | 33 +++- packages/contracts/test/multisig-constants.ts | 4 +- .../test/test-utils/protocol-version.ts | 16 ++ packages/contracts/test/test-utils/storage.ts | 18 ++ .../test/test-utils/typechain-versions.ts | 9 + .../test/test-utils/uups-upgradeable.ts | 185 ++++++++++++++++++ 11 files changed, 417 insertions(+), 118 deletions(-) create mode 100644 packages/contracts/test/test-utils/protocol-version.ts create mode 100644 packages/contracts/test/test-utils/storage.ts create mode 100644 packages/contracts/test/test-utils/typechain-versions.ts create mode 100644 packages/contracts/test/test-utils/uups-upgradeable.ts diff --git a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts index 8639c348..e4d3dc39 100644 --- a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts +++ b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts @@ -1,5 +1,5 @@ import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; -import {AdminSetup__factory, Admin__factory} from '../../typechain'; +import {MultisigSetup__factory, Multisig__factory} from '../../typechain'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -17,9 +17,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get the plugin setup address const setupDeployment = await deployments.get(PLUGIN_SETUP_CONTRACT_NAME); - const setup = AdminSetup__factory.connect(setupDeployment.address, deployer); + const setup = MultisigSetup__factory.connect( + setupDeployment.address, + deployer + ); // Get the plugin implementation address - const implementation = Admin__factory.connect( + const implementation = Multisig__factory.connect( await setup.implementation(), deployer ); diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 3608e253..28e7f77a 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -104,7 +104,7 @@ const config: HardhatUserConfig = { hardhat: { throwOnTransactionFailures: true, throwOnCallFailures: true, - blockGasLimit: BigNumber.from(10).pow(6).mul(30).toNumber(), // 30 million, really high to test some things that are only possible with a higher block gas limit + blockGasLimit: BigNumber.from(10).pow(6).mul(300).toNumber(), // 300 million, really high to test some things that are only possible with a higher block gas limit gasPrice: BigNumber.from(10).pow(9).mul(150).toNumber(), // 150 gwei accounts: getHardhatNetworkAccountsConfig( Object.keys(namedAccounts).length diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index cf6ead5c..7dfef9c6 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -1,38 +1,37 @@ -import {MULTISIG_INTERFACE_ID} from '../../../../../subgraph/src/utils/constants'; +import {createDaoProxy} from '../20_integration-testing/test-helpers'; import { Addresslist__factory, - DAO, - DAO__factory, IERC165Upgradeable__factory, IMembership__factory, IMultisig__factory, IPlugin__factory, IProposal__factory, IProtocolVersion__factory, - Multisig, - Multisig__factory, -} from '../../../../typechain'; -import {Multisig__factory as Multisig_V1_0_0__factory} from '../../../../typechain/@aragon/osx-v1.0.1/plugins/governance/multisig/Multisig.sol'; -import {Multisig__factory as Multisig_V1_3_0__factory} from '../../../../typechain/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol'; -import {ExecutedEvent} from '../../../../typechain/DAO'; -import {ProposalCreatedEvent} from '../../../../typechain/IProposal'; + ProxyFactory__factory, +} from '../../typechain'; +import {ExecutedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/dao/IDAO'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; import { ApprovedEvent, + ProposalCreatedEvent, ProposalExecutedEvent, -} from '../../../../typechain/Multisig'; -import {deployNewDAO} from '../../../test-utils/dao'; -import {osxContractsVersion} from '../../../test-utils/protocol-version'; -import {deployWithProxy} from '../../../test-utils/proxy'; -import { - getProtocolVersion, - deployAndUpgradeFromToCheck, - deployAndUpgradeSelfCheck, -} from '../../../test-utils/uups-upgradeable'; +} from '../../typechain/src/Multisig'; import { MULTISIG_EVENTS, MULTISIG_INTERFACE, MultisigSettings, -} from './multisig-constants'; +} from '../multisig-constants'; +import { + Multisig_V1_0_0__factory, + Multisig_V1_3_0__factory, + Multisig__factory, + Multisig, +} from '../test-utils/typechain-versions'; +import { + deployAndUpgradeFromToCheck, + deployAndUpgradeSelfCheck, + getProtocolVersion, +} from '../test-utils/uups-upgradeable'; import { getInterfaceId, proposalIdToBytes32, @@ -44,10 +43,11 @@ import { findEventTopicLog, TIME, } from '@aragon/osx-commons-sdk'; +import {DAO, DAO__factory} from '@aragon/osx-ethers'; import {time} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {Contract, ContractFactory} from 'ethers'; +import {BigNumber, Contract, ContractFactory} from 'ethers'; import {ethers} from 'hardhat'; export async function approveWithSigners( @@ -88,7 +88,7 @@ describe('Multisig', function () { ethers.utils.toUtf8Bytes('0x123456789') ); - dao = await deployNewDAO(signers[0]); + dao = await createDaoProxy(signers[0], dummyMetadata); }); beforeEach(async function () { @@ -100,8 +100,19 @@ describe('Multisig', function () { onlyListed: true, }; - const MultisigFactory = new Multisig__factory(signers[0]); - multisig = await deployWithProxy(MultisigFactory); + const multisigImplementation = await new Multisig__factory( + signers[0] + ).deploy(); + const multisigProxyFactory = await new ProxyFactory__factory( + signers[0] + ).deploy(multisigImplementation.address); + + const tx = await multisigProxyFactory.deployUUPSProxy([]); + const event = await findEvent( + tx, + multisigProxyFactory.interface.getEvent('ProxyCreated').name + ); + multisig = Multisig__factory.connect(event.args.proxy, signers[0]); dao.grant( dao.address, @@ -115,7 +126,7 @@ describe('Multisig', function () { ); }); - describe('initialize:', async () => { + describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { await multisig.initialize( dao.address, @@ -177,14 +188,17 @@ describe('Multisig', function () { multisigSettings ) ) - .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); }); it('should revert if members list is longer than uint16 max', async () => { - const megaMember = signers[1]; - const members: string[] = new Array(65537).fill(megaMember.address); - await expect(multisig.initialize(dao.address, members, multisigSettings)) + const members = new Array(65536).fill(signers[1].address); + await expect( + multisig.initialize(dao.address, members, multisigSettings, { + gasLimit: BigNumber.from(10).pow(8).toNumber(), + }) + ) .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') .withArgs(65535, members.length); }); @@ -244,7 +258,7 @@ describe('Multisig', function () { expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically }); it('from v1.3.0', async () => { @@ -272,7 +286,7 @@ describe('Multisig', function () { expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically }); }); @@ -324,13 +338,12 @@ describe('Multisig', function () { }); it('supports the `Multisig` interface', async () => { - const iface = getInterfaceId(MULTISIG_INTERFACE); - expect(iface).to.equal(MULTISIG_INTERFACE_ID); // checks that it didn't change - expect(await multisig.supportsInterface(iface)).to.be.true; + const interfaceId = getInterfaceId(MULTISIG_INTERFACE); + expect(await multisig.supportsInterface(interfaceId)).to.be.true; }); }); - describe('updateMultisigSettings:', async () => { + describe('updateMultisigSettings', async () => { beforeEach(async () => { await multisig.initialize( dao.address, @@ -358,12 +371,12 @@ describe('Multisig', function () { it('should emit `MultisigSettingsUpdated` when `updateMutlsigSettings` gets called', async () => { await expect(multisig.updateMultisigSettings(multisigSettings)) - .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); }); }); - describe('isListed:', async () => { + describe('isListed', async () => { it('should return false, if a user is not listed', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -392,7 +405,7 @@ describe('Multisig', function () { }); }); - describe('addAddresses:', async () => { + describe('addAddresses', async () => { it('should add new members to the address list and emit the `MembersAdded` event', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -406,7 +419,7 @@ describe('Multisig', function () { // add a new member await expect(multisig.addAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_ADDED) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersAdded) .withArgs([signers[1].address]); expect(await multisig.isListed(signers[0].address)).to.equal(true); @@ -414,7 +427,7 @@ describe('Multisig', function () { }); }); - describe('removeAddresses:', async () => { + describe('removeAddresses', async () => { it('should remove users from the address list and emit the `MembersRemoved` event', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -428,7 +441,7 @@ describe('Multisig', function () { // remove an existing member await expect(multisig.removeAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_REMOVED) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersRemoved) .withArgs([signers[1].address]); expect(await multisig.isListed(signers[0].address)).to.equal(true); @@ -471,7 +484,7 @@ describe('Multisig', function () { }); }); - describe('createProposal:', async () => { + describe('createProposal', async () => { beforeEach(async () => { multisigSettings.minApprovals = 1; }); @@ -568,7 +581,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -628,7 +641,7 @@ describe('Multisig', function () { await ethers.provider.send('evm_setAutomine', [true]); }); - context('`onlyListed` is set to `false`:', async () => { + context('`onlyListed` is set to `false`', async () => { beforeEach(async () => { multisigSettings.onlyListed = false; @@ -653,7 +666,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[1].address, @@ -666,7 +679,7 @@ describe('Multisig', function () { }); }); - context('`onlyListed` is set to `true`:', async () => { + context('`onlyListed` is set to `true`', async () => { beforeEach(async () => { multisigSettings.onlyListed = true; @@ -767,7 +780,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -812,7 +825,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -822,7 +835,7 @@ describe('Multisig', function () { [], allowFailureMap ) - .to.emit(multisig, MULTISIG_EVENTS.APPROVED) + .to.emit(multisig, MULTISIG_EVENTS.Approved) .withArgs(id, signers[0].address); const block = await ethers.provider.getBlock('latest'); @@ -940,7 +953,7 @@ describe('Multisig', function () { ); }); - describe('canApprove:', async () => { + describe('canApprove', async () => { it('returns `false` if the proposal is already executed', async () => { await approveWithSigners(multisig, id, signers, [0, 1]); @@ -1013,7 +1026,7 @@ describe('Multisig', function () { }); }); - describe('approve:', async () => { + describe('approve', async () => { it('reverts when approving multiple times', async () => { await multisig.approve(id, true); @@ -1088,7 +1101,7 @@ describe('Multisig', function () { }); }); - describe('canExecute:', async () => { + describe('canExecute', async () => { it('returns `false` if the proposal has not reached the minimum approval yet', async () => { const proposal = await multisig.getProposal(id); expect(proposal.approvals).to.be.lt(proposal.parameters.minApprovals); @@ -1157,7 +1170,7 @@ describe('Multisig', function () { }); }); - describe('execute:', async () => { + describe('execute', async () => { it('reverts if the minimum approval is not met', async () => { await expect(multisig.execute(id)) .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') @@ -1205,10 +1218,10 @@ describe('Multisig', function () { findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ) ).to.rejectedWith( - `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); expect(await multisig.canExecute(id)).to.equal(false); @@ -1219,10 +1232,10 @@ describe('Multisig', function () { findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ) ).to.rejectedWith( - `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); // `tryEarlyExecution` is turned on and the vote is decided @@ -1231,7 +1244,7 @@ describe('Multisig', function () { const event = await findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ); expect(event.args.actor).to.equal(multisig.address); @@ -1250,7 +1263,7 @@ describe('Multisig', function () { { const event = await findEvent( tx, - IPROPOSAL_EVENTS.PROPOSAL_EXECUTED + IPROPOSAL_EVENTS.ProposalExecuted ); expect(event.args.proposalId).to.equal(id); } @@ -1265,18 +1278,18 @@ describe('Multisig', function () { await approveWithSigners(multisig, id, signers, [0, 1, 2]); await expect(multisig.connect(signers[3]).execute(id)) - .to.emit(dao, IDAO_EVENTS.EXECUTED) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) - .to.not.emit(multisig, MULTISIG_EVENTS.APPROVED); + .to.emit(dao, IDAO_EVENTS.Executed) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) + .to.not.emit(multisig, MULTISIG_EVENTS.Approved); }); it('emits the `Approved`, `ProposalExecuted`, and `Executed` events if execute is called inside the `approve` method', async () => { await approveWithSigners(multisig, id, signers, [0, 1]); await expect(multisig.connect(signers[2]).approve(id, true)) - .to.emit(dao, IDAO_EVENTS.EXECUTED) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) - .to.emit(multisig, MULTISIG_EVENTS.APPROVED); + .to.emit(dao, IDAO_EVENTS.Executed) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) + .to.emit(multisig, MULTISIG_EVENTS.Approved); }); it("reverts if the proposal hasn't started yet", async () => { diff --git a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts index fc5dbfed..237949a3 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -1,30 +1,18 @@ -import metadata from '../../../../src/plugins/governance/multisig/build-metadata.json'; +import {createDaoProxy} from '../20_integration-testing/test-helpers'; +import metadata from '../../src/build-metadata.json'; import { - DAO, - InterfaceBasedRegistryMock, - InterfaceBasedRegistryMock__factory, - IPluginRepo__factory, - Multisig, MultisigSetup, MultisigSetup__factory, - Multisig__factory, - PluginRepo, - PluginRepo__factory, - PluginSetupProcessor, - PluginSetupProcessor__factory, -} from '../../../../typechain'; -import { - InstallationPreparedEvent, - UpdatePreparedEvent, -} from '../../../../typechain/PluginSetupProcessor'; -import {hashHelpers} from '../../../../utils/psp'; -import {deployNewDAO} from '../../../test-utils/dao'; -import {deployWithProxy} from '../../../test-utils/proxy'; + ProxyFactory__factory, +} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; +import {hashHelpers} from '../../utils/helpers'; import { MULTISIG_INTERFACE, MultisigSettings, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, -} from './multisig-constants'; +} from '../multisig-constants'; +import {Multisig__factory, Multisig} from '../test-utils/typechain-versions'; import { getInterfaceId, findEvent, @@ -33,6 +21,17 @@ import { PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; +import { + DAO, + IPluginRepo__factory, + InterfaceBasedRegistryMock, + InterfaceBasedRegistryMock__factory, + PluginRepo, + PluginRepo__factory, + PluginSetupProcessor, + PluginSetupProcessorEvents, + PluginSetupProcessor__factory, +} from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; @@ -53,7 +52,7 @@ describe('MultisigSetup', function () { before(async () => { signers = await ethers.getSigners(); - targetDao = await deployNewDAO(signers[0]); + targetDao = await createDaoProxy(signers[0], EMPTY_DATA); defaultMultisigSettings = { onlyListed: true, @@ -348,11 +347,22 @@ describe('MultisigSetup', function () { before(async () => { [owner] = await ethers.getSigners(); - managingDAO = await deployNewDAO(owner); + managingDAO = await createDaoProxy(owner, EMPTY_DATA); // Create the PluginRepo - const pluginRepoFactory = new PluginRepo__factory(owner); - pluginRepo = await deployWithProxy(pluginRepoFactory); + const pluginRepoImplementation = await new PluginRepo__factory( + signers[0] + ).deploy(); + const pluginRepoProxyFactory = await new ProxyFactory__factory( + signers[0] + ).deploy(pluginRepoImplementation.address); + const tx = await pluginRepoProxyFactory.deployUUPSProxy([]); + const event = await findEvent( + tx, + pluginRepoProxyFactory.interface.getEvent('ProxyCreated').name + ); + pluginRepo = PluginRepo__factory.connect(event.args.proxy, signers[0]); + await pluginRepo.initialize(owner.address); // Create the PluginRepoRegistry @@ -398,7 +408,7 @@ describe('MultisigSetup', function () { let helpers: string[]; before(async () => { - dao = await deployNewDAO(owner); + dao = await createDaoProxy(owner, EMPTY_DATA); // grant the owner full permission for plugins await dao.applySingleTargetPermissions(psp.address, [ { @@ -439,10 +449,11 @@ describe('MultisigSetup', function () { [[owner.address], [true, 1]] ), }); - const preparedEvent = await findEvent( - tx, - 'InstallationPrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'InstallationPrepared' + ); await expect( psp.applyInstallation(dao.address, { @@ -493,10 +504,11 @@ describe('MultisigSetup', function () { data: '0x00', }, }); - const preparedEvent = await findEvent( - tx, - 'UpdatePrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'UpdatePrepared' + ); await expect( psp.applyUpdate(dao.address, { @@ -524,7 +536,7 @@ describe('MultisigSetup', function () { describe('Release 1 Build 2', () => { before(async () => { - dao = await deployNewDAO(owner); + dao = await createDaoProxy(owner, EMPTY_DATA); // grant the owner full permission for plugins await dao.applySingleTargetPermissions(psp.address, [ { @@ -565,10 +577,11 @@ describe('MultisigSetup', function () { [[owner.address], [true, 1]] ), }); - const preparedEvent = await findEvent( - tx, - 'InstallationPrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'InstallationPrepared' + ); await expect( psp.applyInstallation(dao.address, { diff --git a/packages/contracts/test/20_integration-testing/22_setup-processing.ts b/packages/contracts/test/20_integration-testing/22_setup-processing.ts index 7a84dd6c..87eac0ed 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,8 +1,8 @@ -import {createDaoProxy} from '../10_unit-testing/11_plugin'; import {METADATA, VERSION} from '../../plugin-settings'; -import {AdminSetup, AdminSetup__factory, Admin__factory} from '../../typechain'; +import {MultisigSetup, Multisig__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -import {installPLugin, uninstallPLugin} from './test-helpers'; +import {MultisigSettings} from '../multisig-constants'; +import {createDaoProxy, installPLugin, uninstallPLugin} from './test-helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, @@ -19,6 +19,7 @@ import { PluginSetupProcessorStructs, PluginSetupProcessor__factory, DAO, + MultisigSetup__factory, } from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -29,7 +30,7 @@ const productionNetworkName = getProductionNetworkName(env); describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { it('installs & uninstalls the current build', async () => { - const {alice, deployer, psp, dao, pluginSetupRef} = await loadFixture( + const {alice, bob, deployer, psp, dao, pluginSetupRef} = await loadFixture( fixture ); @@ -53,6 +54,13 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); // Install the current build. + + const initialMembers = [alice.address, bob.address]; + const multisigSettings: MultisigSettings = { + onlyListed: true, + minApprovals: 2, + }; + const results = await installPLugin( deployer, psp, @@ -62,11 +70,11 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs ), - [alice.address] + [initialMembers, multisigSettings] ) ); - const plugin = Admin__factory.connect( + const plugin = Multisig__factory.connect( results.preparedEvent.args.plugin, deployer ); @@ -90,6 +98,9 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio [] ); }); + it.skip('updates to the current build', async () => { + expect(false).to.be.true; + }); }); type FixtureResult = { @@ -99,7 +110,7 @@ type FixtureResult = { dao: DAO; psp: PluginSetupProcessor; pluginRepo: PluginRepo; - pluginSetup: AdminSetup; + pluginSetup: MultisigSetup; pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; }; @@ -136,7 +147,7 @@ async function fixture(): Promise { } const release = 1; - const pluginSetup = AdminSetup__factory.connect( + const pluginSetup = MultisigSetup__factory.connect( (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, deployer ); diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index bdb09c6d..17a3f1ee 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,4 +1,5 @@ -import {IPlugin} from '../../typechain'; +import {IPlugin, ProxyFactory__factory} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, @@ -11,10 +12,12 @@ import { PluginSetupProcessor, DAOStructs, DAO, + DAO__factory, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ContractTransaction} from 'ethers'; +import {ethers} from 'hardhat'; export async function installPLugin( signer: SignerWithAddress, @@ -213,3 +216,31 @@ async function checkPermissions( throw `The used signer does not have the permission with ID '${applyPermissionId}' granted and thus cannot apply the setup`; } } + +// TODO Move into OSX commons ? +export async function createDaoProxy( + deployer: SignerWithAddress, + dummyMetadata: string +): Promise { + const daoImplementation = await new DAO__factory(deployer).deploy(); + const daoProxyFactory = await new ProxyFactory__factory(deployer).deploy( + daoImplementation.address + ); + + const daoInitData = daoImplementation.interface.encodeFunctionData( + 'initialize', + [ + dummyMetadata, + deployer.address, + ethers.constants.AddressZero, + dummyMetadata, + ] + ); + const tx = await daoProxyFactory.deployUUPSProxy(daoInitData); + const event = await findEvent( + tx, + daoProxyFactory.interface.getEvent('ProxyCreated').name + ); + const dao = DAO__factory.connect(event.args.proxy, deployer); + return dao; +} diff --git a/packages/contracts/test/multisig-constants.ts b/packages/contracts/test/multisig-constants.ts index 7175b09c..b908bdcf 100644 --- a/packages/contracts/test/multisig-constants.ts +++ b/packages/contracts/test/multisig-constants.ts @@ -1,8 +1,8 @@ import {ethers} from 'hardhat'; export const MULTISIG_EVENTS = { - MULTISIG_SETTINGS_UPDATED: 'MultisigSettingsUpdated', - APPROVED: 'Approved', + MultisigSettingsUpdated: 'MultisigSettingsUpdated', + Approved: 'Approved', }; export const MULTISIG_INTERFACE = new ethers.utils.Interface([ diff --git a/packages/contracts/test/test-utils/protocol-version.ts b/packages/contracts/test/test-utils/protocol-version.ts new file mode 100644 index 00000000..8b06203a --- /dev/null +++ b/packages/contracts/test/test-utils/protocol-version.ts @@ -0,0 +1,16 @@ +import {version} from '../../package.json'; + +// The protocol version number of contracts not having a `getProtocolVersion()` function because they don't inherit from `ProtocolVersion.sol` yet. +export const IMPLICIT_INITIAL_PROTOCOL_VERSION: [number, number, number] = [ + 1, 0, 0, +]; + +/** + * Returns the NPM version number from the `osx` package.json file + */ +// TODO +export function osxContractsVersion(): [number, number, number] { + const trimmedVersion = version.split('-')[0]; + const semver = trimmedVersion.split('.'); + return [Number(semver[0]), Number(semver[1]), Number(semver[2])]; +} diff --git a/packages/contracts/test/test-utils/storage.ts b/packages/contracts/test/test-utils/storage.ts new file mode 100644 index 00000000..543c0fa1 --- /dev/null +++ b/packages/contracts/test/test-utils/storage.ts @@ -0,0 +1,18 @@ +import {defaultAbiCoder} from 'ethers/lib/utils'; +import {ethers} from 'hardhat'; + +// See https://eips.ethereum.org/EIPS/eip-1967 +export const ERC1967_IMPLEMENTATION_SLOT = + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) + +export const OZ_INITIALIZED_SLOT_POSITION = 0; + +export async function readStorage( + contractAddress: string, + location: number | string, + types: string[] +): Promise { + return ethers.provider + .getStorageAt(contractAddress, location) + .then(encoded => defaultAbiCoder.decode(types, encoded)[0]); +} diff --git a/packages/contracts/test/test-utils/typechain-versions.ts b/packages/contracts/test/test-utils/typechain-versions.ts new file mode 100644 index 00000000..e9f430bd --- /dev/null +++ b/packages/contracts/test/test-utils/typechain-versions.ts @@ -0,0 +1,9 @@ +/// Typechain will sometimes by default link to the wrong version of the contract, when we have name collisions +/// The version specified in src is the factory and contract without the version number. +/// Import as needed in the test files, and use the correct version of the contract. + +export {Multisig__factory as Multisig_V1_0_0__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory as Multisig_V1_3_0__factory} from '../../typechain/factories/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory} from '../../typechain/factories/src/Multisig__factory'; +export {Multisig} from '../../typechain/src/Multisig'; +export {IMultisig} from '../../typechain/src/IMultisig'; diff --git a/packages/contracts/test/test-utils/uups-upgradeable.ts b/packages/contracts/test/test-utils/uups-upgradeable.ts new file mode 100644 index 00000000..fd952f12 --- /dev/null +++ b/packages/contracts/test/test-utils/uups-upgradeable.ts @@ -0,0 +1,185 @@ +import {IMPLICIT_INITIAL_PROTOCOL_VERSION} from './protocol-version'; +import {readStorage, ERC1967_IMPLEMENTATION_SLOT} from './storage'; +import {DAO, PluginRepo} from '@aragon/osx-ethers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import {Contract, ContractFactory, errors} from 'ethers'; +import {upgrades} from 'hardhat'; + +// Deploys a proxy and a new implementation from the same factory and checks that the upgrade works. +export async function deployAndUpgradeSelfCheck( + deployer: SignerWithAddress, + upgrader: SignerWithAddress, + initArgs: any, + initializerName: string, + factory: ContractFactory, + upgradePermissionId: string, + managingContract?: DAO | PluginRepo | undefined +) { + // Deploy proxy and implementation + const proxy = await upgrades.deployProxy( + factory.connect(deployer), + Object.values(initArgs), + { + kind: 'uups', + initializer: initializerName, + unsafeAllow: ['constructor'], + constructorArgs: [], + } + ); + + // Grant the upgrade permission + const grantArgs: [string, string, string] = [ + proxy.address, + upgrader.address, + upgradePermissionId, + ]; + + // Check if the contract is a permission manager itself + if (managingContract === undefined) { + await expect( + upgrades.upgradeProxy(proxy.address, factory.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'Unauthorized') + .withArgs(...grantArgs); + + await proxy.connect(deployer).grant(...grantArgs); + } + // Or if the permission manager is located in a different contract + else { + await expect( + upgrades.upgradeProxy(proxy.address, factory.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'DaoUnauthorized') + .withArgs(managingContract.address, ...grantArgs); + + await managingContract.connect(deployer).grant(...grantArgs); + } + + // Deploy a new implementation (the same contract at a different address) + const toImplementation = (await factory.deploy()).address; + + // Confirm that the two implementations are different + const fromImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + expect(toImplementation).to.not.equal(fromImplementation); + + // Upgrade from the old to the new implementation + await proxy.connect(upgrader).upgradeTo(toImplementation); + + // Confirm that the proxy points to the new implementation + const implementationAfterUpgrade = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + expect(implementationAfterUpgrade).to.equal(toImplementation); +} + +// Deploys a proxy and a new implementation via two different factories and checks that the upgrade works. +export async function deployAndUpgradeFromToCheck( + deployer: SignerWithAddress, + upgrader: SignerWithAddress, + initArgs: any, + initializerName: string, + from: ContractFactory, + to: ContractFactory, + upgradePermissionId: string, + managingDao?: DAO | PluginRepo +): Promise<{ + proxy: Contract; + fromImplementation: string; + toImplementation: string; +}> { + // Deploy proxy and implementation + let proxy = await upgrades.deployProxy( + from.connect(deployer), + Object.values(initArgs), + { + kind: 'uups', + initializer: initializerName, + unsafeAllow: ['constructor'], + constructorArgs: [], + } + ); + + const fromImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + + // Grant the upgrade permission + const grantArgs: [string, string, string] = [ + proxy.address, + upgrader.address, + upgradePermissionId, + ]; + + if (managingDao === undefined) { + await expect( + upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'Unauthorized') + .withArgs(...grantArgs); + + await proxy.connect(deployer).grant(...grantArgs); + } else { + await expect( + upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'DaoUnauthorized') + .withArgs(managingDao.address, ...grantArgs); + + await managingDao.connect(deployer).grant(...grantArgs); + } + + // Upgrade the proxy to a new implementation from a different factory + await upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }); + + const toImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + return {proxy, fromImplementation, toImplementation}; +} + +export async function getProtocolVersion( + contract: Contract +): Promise<[number, number, number]> { + let protocolVersion: [number, number, number]; + try { + contract.interface.getFunction('protocolVersion'); + protocolVersion = await contract.protocolVersion(); + } catch (error: unknown) { + if ( + error instanceof Error && + 'code' in error && + error.code === errors.INVALID_ARGUMENT + ) { + protocolVersion = IMPLICIT_INITIAL_PROTOCOL_VERSION; + } else { + throw error; + } + } + return protocolVersion; +}