From bc4dce78f966134b632a0b035f33c7b9cdfef36d Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 8 Mar 2024 17:11:33 +0400 Subject: [PATCH] tests: moved integration tests and all working except build upgrades --- packages/contracts/hardhat.config.ts | 6 +- packages/contracts/plugin-settings.ts | 4 +- .../20_integration-testing/21_deployment.ts | 255 ++++----- .../22_setup-processing.ts | 496 ++++++++++++------ .../20_integration-testing/test-helpers.ts | 170 +++++- .../31_upgradeability.ts | 38 +- .../test/test-utils/token-voting-constants.ts | 25 + .../test/test-utils/typechain-versions.ts | 4 + 8 files changed, 689 insertions(+), 309 deletions(-) diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index b0a5a67e..318d6596 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -59,7 +59,7 @@ function getHardhatNetworkAccountsConfig( const oneEther = BigNumber.from(10).pow(18); return { privateKey, - balance: oneEther.mul(1000).toString(), + balance: oneEther.mul(10000).toString(), }; } ); @@ -168,7 +168,9 @@ const config: HardhatUserConfig = { tests: './test', deploy: './deploy', }, - + mocha: { + timeout: 60_000, + }, solidity: { version: '0.8.17', settings: { diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index 8f1220f1..f57a6383 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -1,8 +1,8 @@ import buildMetadata from './src/build-metadata.json'; import releaseMetadata from './src/release-metadata.json'; +import {GovernanceERC20} from './test/test-utils/typechain-versions'; import {VersionTag} from '@aragon/osx-commons-sdk'; import {ethers} from 'hardhat'; -import { GovernanceERC20 } from './test/test-utils/typechain-versions'; export const PLUGIN_CONTRACT_NAME = 'TokenVoting'; // This must match the filename `packages/contracts/src/MyPlugin.sol` and the contract name `MyPlugin` within. export const PLUGIN_SETUP_CONTRACT_NAME = 'TokenVotingSetup'; // This must match the filename `packages/contracts/src/MyPluginSetup.sol` and the contract name `MyPluginSetup` within. @@ -13,7 +13,7 @@ export const GOVERNANCE_WRAPPED_ERC20_CONTRACT_NAME = 'GovernanceWrappedERC20'; export const VERSION: VersionTag = { release: 1, // Increment this number ONLY if breaking/incompatible changes were made. Updates between releases are NOT possible. - build: 1, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. + build: 3, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. }; /* DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING */ diff --git a/packages/contracts/test/20_integration-testing/21_deployment.ts b/packages/contracts/test/20_integration-testing/21_deployment.ts index f8a6f657..eb6509e9 100644 --- a/packages/contracts/test/20_integration-testing/21_deployment.ts +++ b/packages/contracts/test/20_integration-testing/21_deployment.ts @@ -1,123 +1,132 @@ -// import {METADATA, VERSION} from '../../plugin-settings'; -// import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -// import { -// getLatestNetworkDeployment, -// getNetworkNameByAlias, -// } from '@aragon/osx-commons-configs'; -// import { -// DAO_PERMISSIONS, -// PERMISSION_MANAGER_FLAGS, -// PLUGIN_REPO_PERMISSIONS, -// UnsupportedNetworkError, -// toHex, -// uploadToIPFS, -// } from '@aragon/osx-commons-sdk'; -// import { -// PluginRepo, -// PluginRepoRegistry, -// PluginRepoRegistry__factory, -// } from '@aragon/osx-ethers'; -// import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; -// import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -// import {expect} from 'chai'; -// import env, {deployments, ethers} from 'hardhat'; - -// const productionNetworkName = getProductionNetworkName(env); - -// describe(`Deployment on network '${productionNetworkName}'`, function () { -// it('creates the repo', async () => { -// const {pluginRepo, pluginRepoRegistry} = await loadFixture(fixture); - -// expect(await pluginRepoRegistry.entries(pluginRepo.address)).to.be.true; -// }); - -// it('makes the deployer the repo maintainer', async () => { -// const {deployer, pluginRepo} = await loadFixture(fixture); - -// expect( -// await pluginRepo.isGranted( -// pluginRepo.address, -// deployer.address, -// DAO_PERMISSIONS.ROOT_PERMISSION_ID, -// PERMISSION_MANAGER_FLAGS.NO_CONDITION -// ) -// ).to.be.true; - -// expect( -// await pluginRepo.isGranted( -// pluginRepo.address, -// deployer.address, -// PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, -// PERMISSION_MANAGER_FLAGS.NO_CONDITION -// ) -// ).to.be.true; - -// expect( -// await pluginRepo.isGranted( -// pluginRepo.address, -// deployer.address, -// PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, -// PERMISSION_MANAGER_FLAGS.NO_CONDITION -// ) -// ).to.be.true; -// }); - -// context('PluginSetup Publication', async () => { -// it('registers the setup', async () => { -// const {pluginRepo} = await loadFixture(fixture); - -// await pluginRepo['getVersion((uint8,uint16))']({ -// release: VERSION.release, -// build: VERSION.build, -// }); - -// const results = await pluginRepo['getVersion((uint8,uint16))']({ -// release: VERSION.release, -// build: VERSION.build, -// }); - -// const buildMetadataURI = `ipfs://${await uploadToIPFS( -// JSON.stringify(METADATA.build, null, 2) -// )}`; - -// expect(results.buildMetadata).to.equal(toHex(buildMetadataURI)); -// }); -// }); -// }); - -// type FixtureResult = { -// deployer: SignerWithAddress; -// pluginRepo: PluginRepo; -// pluginRepoRegistry: PluginRepoRegistry; -// }; - -// async function fixture(): Promise { -// // Deploy all -// const tags = ['CreateRepo', 'NewVersion']; -// await deployments.fixture(tags); - -// const [deployer] = await ethers.getSigners(); - -// // Plugin Repo -// const {pluginRepo, ensDomain} = await findPluginRepo(env); -// if (pluginRepo === null) { -// throw `PluginRepo '${ensDomain}' does not exist yet.`; -// } - -// 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}.`; -// } - -// // Plugin repo registry -// const pluginRepoRegistry = PluginRepoRegistry__factory.connect( -// networkDeployments.PluginRepoRegistryProxy.address, -// deployer -// ); - -// return {deployer, pluginRepo, pluginRepoRegistry}; -// } +import {METADATA, VERSION} from '../../plugin-settings'; +import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; +import { + getLatestNetworkDeployment, + getNetworkNameByAlias, +} from '@aragon/osx-commons-configs'; +import { + DAO_PERMISSIONS, + PERMISSION_MANAGER_FLAGS, + PLUGIN_REPO_PERMISSIONS, + UnsupportedNetworkError, + toHex, + uploadToIPFS, +} from '@aragon/osx-commons-sdk'; +import { + DAO, + DAO__factory, + PluginRepo, + PluginRepoRegistry, + PluginRepoRegistry__factory, +} from '@aragon/osx-ethers'; +import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import env, {deployments, ethers} from 'hardhat'; + +const productionNetworkName = getProductionNetworkName(env); + +describe(`Deployment on network '${productionNetworkName}'`, function () { + it('creates the repo', async () => { + const {pluginRepo, pluginRepoRegistry} = await loadFixture(fixture); + + expect(await pluginRepoRegistry.entries(pluginRepo.address)).to.be.true; + }); + + it('gives the management DAO permissions over the repo', async () => { + const {pluginRepo, managementDaoProxy} = await loadFixture(fixture); + + expect( + await pluginRepo.isGranted( + pluginRepo.address, + managementDaoProxy.address, + DAO_PERMISSIONS.ROOT_PERMISSION_ID, + PERMISSION_MANAGER_FLAGS.NO_CONDITION + ) + ).to.be.true; + + expect( + await pluginRepo.isGranted( + pluginRepo.address, + managementDaoProxy.address, + PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + PERMISSION_MANAGER_FLAGS.NO_CONDITION + ) + ).to.be.true; + + expect( + await pluginRepo.isGranted( + pluginRepo.address, + managementDaoProxy.address, + PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + PERMISSION_MANAGER_FLAGS.NO_CONDITION + ) + ).to.be.true; + }); + + context('PluginSetup Publication', async () => { + it('registers the setup', async () => { + const {pluginRepo} = await loadFixture(fixture); + + await pluginRepo['getVersion((uint8,uint16))']({ + release: VERSION.release, + build: VERSION.build, + }); + + const results = await pluginRepo['getVersion((uint8,uint16))']({ + release: VERSION.release, + build: VERSION.build, + }); + + const buildMetadataURI = `ipfs://${await uploadToIPFS( + JSON.stringify(METADATA.build, null, 2) + )}`; + + expect(results.buildMetadata).to.equal(toHex(buildMetadataURI)); + }); + }); +}); + +type FixtureResult = { + deployer: SignerWithAddress; + pluginRepo: PluginRepo; + pluginRepoRegistry: PluginRepoRegistry; + managementDaoProxy: DAO; +}; + +async function fixture(): Promise { + // Deploy all + const tags = ['CreateRepo', 'NewVersion', 'TransferOwnershipToManagmentDao']; + + await deployments.fixture(tags); + const [deployer] = await ethers.getSigners(); + + // Plugin repo + const {pluginRepo, ensDomain} = await findPluginRepo(env); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + + 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}.`; + } + + // Plugin repo registry + const pluginRepoRegistry = PluginRepoRegistry__factory.connect( + networkDeployments.PluginRepoRegistryProxy.address, + deployer + ); + + // Management DAO proxy + const managementDaoProxy = DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); + + return {deployer, pluginRepo, pluginRepoRegistry, managementDaoProxy}; +} 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 490aae27..93778eac 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,150 +1,346 @@ -// import {METADATA} from '../../plugin-settings'; -// import { -// DAOMock, -// DAOMock__factory, -// MyPluginSetup, -// MyPluginSetup__factory, -// MyPlugin__factory, -// } from '../../typechain'; -// import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -// import {installPLugin, uninstallPLugin} from './test-helpers'; -// import { -// getLatestNetworkDeployment, -// getNetworkNameByAlias, -// } from '@aragon/osx-commons-configs'; -// import { -// UnsupportedNetworkError, -// getNamedTypesFromMetadata, -// } from '@aragon/osx-commons-sdk'; -// import { -// PluginSetupProcessor, -// PluginRepo, -// PluginSetupProcessorStructs, -// PluginSetupProcessor__factory, -// } from '@aragon/osx-ethers'; -// import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; -// import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -// import {expect} from 'chai'; -// import {BigNumber} from 'ethers'; -// import env, {deployments, ethers} from 'hardhat'; - -// const productionNetworkName = getProductionNetworkName(env); - -// describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { -// it('installs & uninstalls the current build', async () => { -// const {deployer, psp, daoMock, pluginSetup, pluginSetupRef} = -// await loadFixture(fixture); - -// // Allow all authorized calls to happen -// await daoMock.setHasPermissionReturnValueMock(true); - -// // Install the current build. -// const results = await installPLugin( -// deployer, -// psp, -// daoMock, -// pluginSetupRef, -// ethers.utils.defaultAbiCoder.encode( -// getNamedTypesFromMetadata( -// METADATA.build.pluginSetup.prepareInstallation.inputs -// ), -// [123] -// ) -// ); - -// const plugin = MyPlugin__factory.connect( -// results.preparedEvent.args.plugin, -// deployer -// ); - -// // Check implementation. -// expect(await plugin.implementation()).to.be.eq( -// await pluginSetup.implementation() -// ); - -// // Check state. -// expect(await plugin.number()).to.eq(123); - -// // Uninstall the current build. -// await uninstallPLugin( -// deployer, -// psp, -// daoMock, -// plugin, -// pluginSetupRef, -// ethers.utils.defaultAbiCoder.encode( -// getNamedTypesFromMetadata( -// METADATA.build.pluginSetup.prepareUninstallation.inputs -// ), -// [] -// ), -// [] -// ); -// }); -// }); - -// type FixtureResult = { -// deployer: SignerWithAddress; -// alice: SignerWithAddress; -// bob: SignerWithAddress; -// daoMock: DAOMock; -// psp: PluginSetupProcessor; -// pluginRepo: PluginRepo; -// pluginSetup: MyPluginSetup; -// pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; -// }; - -// async function fixture(): Promise { -// // Deploy all contracts -// const tags = ['CreateRepo', 'NewVersion']; -// await deployments.fixture(tags); - -// const [deployer, alice, bob] = await ethers.getSigners(); -// const daoMock = await new DAOMock__factory(deployer).deploy(); - -// 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}.`; -// } - -// // Get the `PluginSetupProcessor` from the network -// const psp = PluginSetupProcessor__factory.connect( -// networkDeployments.PluginSetupProcessor.address, -// deployer -// ); - -// // Get the deployed `PluginRepo` -// const {pluginRepo, ensDomain} = await findPluginRepo(env); -// if (pluginRepo === null) { -// throw `PluginRepo '${ensDomain}' does not exist yet.`; -// } - -// const release = 1; -// const pluginSetup = MyPluginSetup__factory.connect( -// (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, -// deployer -// ); - -// const pluginSetupRef = { -// versionTag: { -// release: BigNumber.from(1), -// build: BigNumber.from(1), -// }, -// pluginSetupRepo: pluginRepo.address, -// }; - -// return { -// deployer, -// alice, -// bob, -// psp, -// daoMock, -// pluginRepo, -// pluginSetup, -// pluginSetupRef, -// }; -// } +import {METADATA, VERSION} from '../../plugin-settings'; +import {governance} from '../../typechain/@openzeppelin/contracts-upgradeable'; +import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; +import { + DEFAULT_VOTING_SETTINGS, + TokenVotingSettings, + spreadSettings, +} from '../test-utils/token-voting-constants'; +import { + GovernanceERC20__factory, + TokenVotingSetup, + TokenVotingSetup__factory, +} from '../test-utils/typechain-versions'; +import { + createDaoProxy, + installPLugin, + uninstallPLugin, + updateFromBuildTest, +} from './test-helpers'; +import { + getLatestNetworkDeployment, + getNetworkNameByAlias, +} from '@aragon/osx-commons-configs'; +import { + DAO_PERMISSIONS, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS, + TIME, + UnsupportedNetworkError, + getNamedTypesFromMetadata, + pctToRatio, +} from '@aragon/osx-commons-sdk'; +import { + PluginSetupProcessor, + PluginRepo, + PluginSetupProcessorStructs, + PluginSetupProcessor__factory, + DAO, + TokenVoting__factory, +} from '@aragon/osx-ethers'; +import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import env, {deployments, ethers} from 'hardhat'; + +const productionNetworkName = getProductionNetworkName(env); + +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + dao: DAO; + defaultInitData: TokenVotingSettings; + psp: PluginSetupProcessor; + pluginRepo: PluginRepo; + pluginSetup: TokenVotingSetup; + pluginSetupRefLatestBuild: PluginSetupProcessorStructs.PluginSetupRefStruct; +}; + +async function fixture(): Promise { + // Deploy all contracts + const tags = ['CreateRepo', 'NewVersion']; + await deployments.fixture(tags); + + const [deployer, alice, bob] = await ethers.getSigners(); + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); + + 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}.`; + } + + // Get the `PluginSetupProcessor` from the network + const psp = PluginSetupProcessor__factory.connect( + networkDeployments.PluginSetupProcessor.address, + deployer + ); + + const erc20 = new GovernanceERC20__factory(deployer); + const governanceErc20 = await erc20.deploy( + dao.address, + 'GovernanceERC20', + 'GOV', + { + receivers: [deployer.address], + amounts: ['100'], + } + ); + // Get the deployed `PluginRepo` + const {pluginRepo, ensDomain} = await findPluginRepo(env); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + + const release = 1; + const latestVersion = await pluginRepo['getLatestVersion(uint8)'](release); + + console.log('latestVersion', latestVersion); + const pluginSetup = TokenVotingSetup__factory.connect( + latestVersion.pluginSetup, + deployer + ); + + const defaultInitData = { + dao: dao.address, + votingSettings: DEFAULT_VOTING_SETTINGS, + token: governanceErc20.address, + }; + + const pluginSetupRefLatestBuild = { + versionTag: { + release: VERSION.release, + build: VERSION.build, + }, + pluginSetupRepo: pluginRepo.address, + }; + + return { + deployer, + alice, + bob, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetup, + pluginSetupRefLatestBuild, + }; +} + +describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { + it('installs & uninstalls the current build with a token', async () => { + const { + alice, + bob, + deployer, + psp, + dao, + pluginSetupRefLatestBuild, + defaultInitData, + } = await loadFixture(fixture); + + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UNINSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); + + const prepareInstallData = { + votingSettings: Object.values(DEFAULT_VOTING_SETTINGS), + tokenSettings: [defaultInitData.token, 'testToken', 'TEST'], + mintSettings: [[], []], + }; + + const prepareInstallInputType = getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ); + + const results = await installPLugin( + deployer, + psp, + dao, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + prepareInstallInputType, + Object.values(prepareInstallData) + ) + ); + + const plugin = TokenVoting__factory.connect( + results.preparedEvent.args.plugin, + deployer + ); + + const pluginToken = await plugin.getVotingToken(); + + // we used an existing token so the deployer (who was minted tokens) + // in the test fixture will be a member, but alice won't be + expect(await plugin.isMember(alice.address)).to.be.false; + expect(await plugin.isMember(deployer.address)).to.be.true; + + // Uninstall the current build. + await uninstallPLugin( + deployer, + psp, + dao, + plugin, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareUninstallation.inputs + ), + [] + ), + [pluginToken] + ); + }); + + it('installs & uninstalls the current build without a token', async () => { + const {alice, bob, deployer, psp, dao, pluginSetupRefLatestBuild} = + await loadFixture(fixture); + + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UNINSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); + + const prepareInstallData = { + votingSettings: Object.values(DEFAULT_VOTING_SETTINGS), + tokenSettings: [ethers.constants.AddressZero, 'testToken', 'TEST'], + mintSettings: [[alice.address], ['1000']], + }; + + const prepareInstallInputType = getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ); + + const results = await installPLugin( + deployer, + psp, + dao, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + prepareInstallInputType, + Object.values(prepareInstallData) + ) + ); + + const plugin = TokenVoting__factory.connect( + results.preparedEvent.args.plugin, + deployer + ); + + const pluginToken = await plugin.getVotingToken(); + + // We didn't pass a token so one was created and the deployer (who was minted tokens) + // is not yet a member, but alice is - as the mint settings were set to mint tokens for her + expect(await plugin.isMember(alice.address)).to.be.true; + expect(await plugin.isMember(deployer.address)).to.be.false; + + // Uninstall the current build. + await uninstallPLugin( + deployer, + psp, + dao, + plugin, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareUninstallation.inputs + ), + [] + ), + [pluginToken] + ); + }); + + it.only('updates from build 1 to the current build', async () => { + const { + deployer, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetupRefLatestBuild, + } = await loadFixture(fixture); + + const prepareInstallData = { + votingSettings: Object.values(DEFAULT_VOTING_SETTINGS), + tokenSettings: [defaultInitData.token, 'testToken', 'TEST'], + mintSettings: [[], []], + }; + + await updateFromBuildTest( + dao, + deployer, + psp, + pluginRepo, + pluginSetupRefLatestBuild, + 1, + Object.values(prepareInstallData), + [] + ); + }); + + it('updates from build 2 to the current build', async () => { + const { + deployer, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetupRefLatestBuild, + } = await loadFixture(fixture); + + const prepareInstallData = { + votingSettings: Object.values(DEFAULT_VOTING_SETTINGS), + tokenSettings: [defaultInitData.token, 'testToken', 'TEST'], + mintSettings: [[], []], + }; + + await updateFromBuildTest( + dao, + deployer, + psp, + pluginRepo, + pluginSetupRefLatestBuild, + 2, + Object.values(prepareInstallData), + [] + ); + }); +}); diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index a8712834..6d177fcc 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,24 +1,37 @@ -import {DAOMock, IPlugin} from '../../typechain'; +import {METADATA, VERSION} from '../../plugin-settings'; +import { + IPlugin, + PluginSetup__factory, + ProxyFactory__factory, +} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; +import {PluginUUPSUpgradeable__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/core/plugin'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, PLUGIN_SETUP_PROCESSOR_PERMISSIONS, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, findEvent, + getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; import { PluginSetupProcessorEvents, PluginSetupProcessorStructs, PluginSetupProcessor, DAOStructs, + DAO, + DAO__factory, + PluginRepo, } 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, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string ): Promise<{ @@ -28,8 +41,8 @@ export async function installPLugin( appliedEvent: PluginSetupProcessorEvents.InstallationAppliedEvent; }> { const prepareTx = await psp.connect(signer).prepareInstallation(dao.address, { - pluginSetupRef: pluginSetupRef, - data: data, + pluginSetupRef, + data, }); const preparedEvent = @@ -41,6 +54,7 @@ export async function installPLugin( const plugin = preparedEvent.args.plugin; const preparedPermissions = preparedEvent.args.preparedSetupData.permissions; + console.log('1b'); await checkPermissions( preparedPermissions, dao, @@ -50,8 +64,8 @@ export async function installPLugin( ); const applyTx = await psp.connect(signer).applyInstallation(dao.address, { - pluginSetupRef: pluginSetupRef, - plugin: plugin, + pluginSetupRef, + plugin, permissions: preparedPermissions, helpersHash: hashHelpers(preparedEvent.args.preparedSetupData.helpers), }); @@ -68,7 +82,7 @@ export async function installPLugin( export async function uninstallPLugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string, @@ -123,7 +137,7 @@ export async function uninstallPLugin( export async function updatePlugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, currentHelpers: string[], pluginSetupRefCurrent: PluginSetupProcessorStructs.PluginSetupRefStruct, @@ -183,7 +197,7 @@ export async function updatePlugin( async function checkPermissions( preparedPermissions: DAOStructs.MultiTargetPermissionStruct[], - dao: DAOMock, + dao: DAO, psp: PluginSetupProcessor, signer: SignerWithAddress, applyPermissionId: string @@ -212,3 +226,141 @@ async function checkPermissions( throw `The used signer does not have the permission with ID '${applyPermissionId}' granted and thus cannot apply the setup`; } } + +export async function updateFromBuildTest( + dao: DAO, + deployer: SignerWithAddress, + psp: PluginSetupProcessor, + pluginRepo: PluginRepo, + pluginSetupRefLatestBuild: PluginSetupProcessorStructs.PluginSetupRefStruct, + build: number, + installationInputs: any[], + updateInputs: any[] +) { + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UPDATE_PERMISSION_ID + ); + + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); + + // Install build + const pluginSetupRefBuild1 = { + versionTag: { + release: VERSION.release, + build, + }, + pluginSetupRepo: pluginRepo.address, + }; + const installationResults = await installPLugin( + deployer, + psp, + dao, + pluginSetupRefBuild1, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ), + installationInputs + ) + ); + + // Get the plugin address. + const plugin = PluginUUPSUpgradeable__factory.connect( + installationResults.preparedEvent.args.plugin, + deployer + ); + + // Check that the implementation of the plugin proxy matches the latest build + const implementationBuild1 = await PluginSetup__factory.connect( + ( + await pluginRepo['getVersion((uint8,uint16))']( + pluginSetupRefBuild1.versionTag + ) + ).pluginSetup, + deployer + ).implementation(); + + console.log('3'); + expect(await plugin.implementation()).to.equal(implementationBuild1); + + // Grant the PSP the permission to upgrade the plugin implementation. + await dao + .connect(deployer) + .grant( + plugin.address, + psp.address, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID + ); + + // Update build 1 to the latest build + await expect( + updatePlugin( + deployer, + psp, + dao, + plugin, + installationResults.preparedEvent.args.preparedSetupData.helpers, + pluginSetupRefBuild1, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareUpdate[1].inputs + ), + updateInputs + ) + ) + ).to.not.be.reverted; + + // Check that the implementation of the plugin proxy matches the latest build + const implementationLatestBuild = await PluginSetup__factory.connect( + ( + await pluginRepo['getVersion((uint8,uint16))']( + pluginSetupRefLatestBuild.versionTag + ) + ).pluginSetup, + deployer + ).implementation(); + expect(await plugin.implementation()).to.equal(implementationLatestBuild); +} + +// TODO Move into OSX commons as part of Task OS-928. +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/30_regression-testing/31_upgradeability.ts b/packages/contracts/test/30_regression-testing/31_upgradeability.ts index 86720694..c83cd456 100644 --- a/packages/contracts/test/30_regression-testing/31_upgradeability.ts +++ b/packages/contracts/test/30_regression-testing/31_upgradeability.ts @@ -1,4 +1,9 @@ import {createDaoProxy} from '../test-utils/dao'; +import { + DEFAULT_VOTING_SETTINGS, + TokenVotingSettings, + spreadSettings, +} from '../test-utils/token-voting-constants'; import { TokenVoting_V1_0_0__factory, TokenVoting_V1_3_0__factory, @@ -9,18 +14,12 @@ import { deployAndUpgradeSelfCheck, getProtocolVersion, } from '../test-utils/uups-upgradeable'; -import {VotingMode, VotingSettings} from '../test-utils/voting-helpers'; -import { - PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, - TIME, - pctToRatio, -} from '@aragon/osx-commons-sdk'; +import {PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS} from '@aragon/osx-commons-sdk'; import {DAO, TestGovernanceERC20__factory} from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; -import {Address} from 'hardhat-deploy/types'; describe('Upgrades', () => { it('upgrades to a new implementation', async () => { @@ -30,7 +29,7 @@ describe('Upgrades', () => { await deployAndUpgradeSelfCheck( deployer, alice, - defaultInitData, + spreadSettings(defaultInitData), 'initialize', currentContractFactory, PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, @@ -47,7 +46,7 @@ describe('Upgrades', () => { await deployAndUpgradeFromToCheck( deployer, alice, - defaultInitData, + spreadSettings(defaultInitData), 'initialize', legacyContractFactory, currentContractFactory, @@ -78,7 +77,7 @@ describe('Upgrades', () => { await deployAndUpgradeFromToCheck( deployer, alice, - defaultInitData, + spreadSettings(defaultInitData), 'initialize', legacyContractFactory, currentContractFactory, @@ -101,13 +100,12 @@ describe('Upgrades', () => { }); }); -type InitDataTokenVoting = [Address, VotingSettings, Address]; type FixtureResult = { deployer: SignerWithAddress; alice: SignerWithAddress; bob: SignerWithAddress; carol: SignerWithAddress; - defaultInitData: InitDataTokenVoting; + defaultInitData: TokenVotingSettings; dao: DAO; }; @@ -131,17 +129,11 @@ async function fixture(): Promise { ); // Create an initialized plugin clone - const defaultInitData = [ - dao.address, - { - votingMode: VotingMode.EarlyExecution, - supportThreshold: pctToRatio(50), - minParticipation: pctToRatio(20), - minDuration: TIME.HOUR, - minProposerVotingPower: 0, - }, - governanceErc20Mock.address, - ] as InitDataTokenVoting; + const defaultInitData = { + dao: dao.address, + votingSettings: DEFAULT_VOTING_SETTINGS, + token: governanceErc20Mock.address, + }; return { deployer, diff --git a/packages/contracts/test/test-utils/token-voting-constants.ts b/packages/contracts/test/test-utils/token-voting-constants.ts index 751d1c36..3c12b6f1 100644 --- a/packages/contracts/test/test-utils/token-voting-constants.ts +++ b/packages/contracts/test/test-utils/token-voting-constants.ts @@ -1,7 +1,32 @@ +import {VotingMode, VotingSettings} from './voting-helpers'; +import {TIME, pctToRatio} from '@aragon/osx-commons-sdk'; import {ethers} from 'hardhat'; +import {Address} from 'hardhat-deploy/types'; export const TOKEN_VOTING_INTERFACE = new ethers.utils.Interface([ 'function initialize(address,tuple(uint8,uint32,uint32,uint64,uint256),address)', 'function getVotingToken()', ]); export const TOKEN_VOTING_INTERFACE_ID = '0x50eb001e'; + +export const DEFAULT_VOTING_SETTINGS: VotingSettings = { + votingMode: VotingMode.EarlyExecution, + supportThreshold: pctToRatio(50), + minParticipation: pctToRatio(20), + minDuration: TIME.HOUR, + minProposerVotingPower: 0, +}; + +export type TokenVotingSettings = { + dao: Address; + votingSettings: VotingSettings; + token: Address; +}; + +export const spreadSettings = ( + settings: TokenVotingSettings +): [Address, VotingSettings, Address] => [ + settings.dao, + settings.votingSettings, + settings.token, +]; diff --git a/packages/contracts/test/test-utils/typechain-versions.ts b/packages/contracts/test/test-utils/typechain-versions.ts index d4ef5326..2e52aee3 100644 --- a/packages/contracts/test/test-utils/typechain-versions.ts +++ b/packages/contracts/test/test-utils/typechain-versions.ts @@ -8,6 +8,10 @@ export {TokenVoting__factory as TokenVoting_V1_3_0__factory} from '../../typecha export {TokenVoting__factory} from '../../typechain/factories/src/TokenVoting__factory'; export {TokenVoting} from '../../typechain/src/TokenVoting'; +/* TokenVotingSetup */ +export {TokenVotingSetup__factory} from '../../typechain'; +export {TokenVotingSetup} from '../../typechain'; + /* Governance ERC20 */ export {GovernanceERC20__factory as GovernanceERC20_V1_0_0__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/token/ERC20/governance/GovernanceERC20__factory'; export {GovernanceERC20__factory as GovernanceERC20_V1_3_0__factory} from '../../typechain/factories/@aragon/osx-v1.3.0/token/ERC20/governance/GovernanceERC20__factory';