From 4f86c7ccb6c5cd919b2b125906147930d95c81be Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 10:39:43 +0400 Subject: [PATCH 1/9] metadata extension --- packages/contracts/hardhat.config.ts | 1 - packages/contracts/src/MajorityVotingBase.sol | 13 +++++- packages/contracts/src/TokenVoting.sol | 45 ++++++++++++++----- packages/contracts/src/TokenVotingSetup.sol | 45 +++++++++---------- packages/contracts/src/build-metadata.json | 20 +++++++-- .../src/mocks/MajorityVotingMock.sol | 5 ++- .../test/10_unit-testing/11_plugin.ts | 21 +++++++-- .../test/10_unit-testing/12_plugin-setup.ts | 37 ++++++++++----- .../base/11_majority-voting.ts | 18 +++++--- .../22_setup-processing.ts | 14 +++++- .../31_upgradeability.ts | 20 +++++++-- .../test/test-utils/token-voting-constants.ts | 2 +- 12 files changed, 173 insertions(+), 68 deletions(-) diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 5b144e7e..1f7e130f 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -161,7 +161,6 @@ const config: HardhatUserConfig = { solidity: { version: '0.8.17', settings: { - viaIR: true, metadata: { // Not including the metadata hash // https://github.com/paulrberg/hardhat-template/issues/31 diff --git a/packages/contracts/src/MajorityVotingBase.sol b/packages/contracts/src/MajorityVotingBase.sol index 678c11f3..aa7c6493 100644 --- a/packages/contracts/src/MajorityVotingBase.sol +++ b/packages/contracts/src/MajorityVotingBase.sol @@ -14,6 +14,7 @@ import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/Pl import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; import {IProposal} from "@aragon/osx-commons-contracts/src/plugin/extensions/proposal/IProposal.sol"; import {Action} from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol"; +import {MetadataExtensionUpgradeable} from "@aragon/osx-commons-contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol"; import {IMajorityVoting} from "./IMajorityVoting.sol"; @@ -122,6 +123,7 @@ abstract contract MajorityVotingBase is IMajorityVoting, Initializable, ERC165Upgradeable, + MetadataExtensionUpgradeable, PluginUUPSUpgradeable, ProposalUpgradeable { @@ -300,12 +302,14 @@ abstract contract MajorityVotingBase is IDAO _dao, VotingSettings calldata _votingSettings, TargetConfig calldata _targetConfig, - uint256 _minApprovals + uint256 _minApprovals, + bytes calldata _metadata ) internal onlyInitializing { __PluginUUPSUpgradeable_init(_dao); _updateVotingSettings(_votingSettings); _updateMinApprovals(_minApprovals); _setTargetConfig(_targetConfig); + _updateMetadata(_metadata); } /// @notice Checks if this or the parent contract supports an interface by its ID. @@ -317,7 +321,12 @@ abstract contract MajorityVotingBase is public view virtual - override(ERC165Upgradeable, PluginUUPSUpgradeable, ProposalUpgradeable) + override( + ERC165Upgradeable, + MetadataExtensionUpgradeable, + PluginUUPSUpgradeable, + ProposalUpgradeable + ) returns (bool) { // In addition to the current IMajorityVoting interface, also support previous version diff --git a/packages/contracts/src/TokenVoting.sol b/packages/contracts/src/TokenVoting.sol index 8bfefa71..6cbe0580 100644 --- a/packages/contracts/src/TokenVoting.sol +++ b/packages/contracts/src/TokenVoting.sol @@ -46,9 +46,10 @@ contract TokenVoting is IMembership, MajorityVotingBase { VotingSettings calldata _votingSettings, IVotesUpgradeable _token, TargetConfig calldata _targetConfig, - uint256 _minApprovals + uint256 _minApprovals, + bytes calldata _metadata ) external onlyCallAtInitialization reinitializer(2) { - __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals); + __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _metadata); votingToken = _token; @@ -61,13 +62,14 @@ contract TokenVoting is IMembership, MajorityVotingBase { /// @param _initData The initialization data to be passed to via `upgradeToAndCall` (see [ERC-1967](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Upgrade)). function initializeFrom(uint16 _fromBuild, bytes calldata _initData) external reinitializer(2) { if (_fromBuild < 3) { - (uint256 minApprovals, TargetConfig memory targetConfig) = abi.decode( - _initData, - (uint256, TargetConfig) - ); + (uint256 minApprovals, TargetConfig memory targetConfig, bytes memory metadata) = abi + .decode(_initData, (uint256, TargetConfig, bytes)); + _updateMinApprovals(minApprovals); _setTargetConfig(targetConfig); + + _updateMetadata(metadata); } } @@ -158,14 +160,13 @@ contract TokenVoting is IMembership, MajorityVotingBase { vote(proposalId, _voteOption, _tryEarlyExecution); } - emit ProposalCreated( - proposalId, - _msgSender(), - _startDate, - _endDate, + _emitProposalCreatedEvent( _metadata, _actions, - _allowFailureMap + _allowFailureMap, + proposalId, + _startDate, + _endDate ); } @@ -311,6 +312,26 @@ contract TokenVoting is IMembership, MajorityVotingBase { return true; } + /// @dev Helper function to avoid stack too deep in non via-ir compilation mode. + function _emitProposalCreatedEvent( + bytes calldata _metadata, + Action[] calldata _actions, + uint256 _allowFailureMap, + uint256 proposalId, + uint64 _startDate, + uint64 _endDate + ) private { + emit ProposalCreated( + proposalId, + _msgSender(), + _startDate, + _endDate, + _metadata, + _actions, + _allowFailureMap + ); + } + /// @dev This empty reserved space is put in place to allow future versions to add new /// variables without shifting down storage in the inheritance chain. /// https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps diff --git a/packages/contracts/src/TokenVotingSetup.sol b/packages/contracts/src/TokenVotingSetup.sol index 14b8973b..2fc432e8 100644 --- a/packages/contracts/src/TokenVotingSetup.sol +++ b/packages/contracts/src/TokenVotingSetup.sol @@ -104,7 +104,8 @@ contract TokenVotingSetup is PluginUpgradeableSetup { // only used for GovernanceERC20(token is not passed) GovernanceERC20.MintSettings memory mintSettings, PluginUUPSUpgradeable.TargetConfig memory targetConfig, - uint256 minApprovals + uint256 minApprovals, + bytes memory metadata ) = abi.decode( _data, ( @@ -112,17 +113,14 @@ contract TokenVotingSetup is PluginUpgradeableSetup { TokenSettings, GovernanceERC20.MintSettings, PluginUUPSUpgradeable.TargetConfig, - uint256 + uint256, + bytes ) ); address token = tokenSettings.addr; - bool tokenAddressNotZero = token != address(0); - // Prepare helpers. - address[] memory helpers = new address[](2); - - if (tokenAddressNotZero) { + if (tokenSettings.addr != address(0)) { if (!token.isContract()) { revert TokenNotContract(token); } @@ -153,28 +151,29 @@ contract TokenVotingSetup is PluginUpgradeableSetup { ); } - // Prepare and deploy plugin proxy. plugin = address(tokenVotingBase).deployUUPSProxy( - abi.encodeWithSignature( - "initialize(address,(uint8,uint32,uint32,uint64,uint256),address,(address,uint8),uint256)", - IDAO(_dao), - votingSettings, - IVotesUpgradeable(token), - targetConfig, - minApprovals + abi.encodeCall( + TokenVoting.initialize, + ( + IDAO(_dao), + votingSettings, + IVotesUpgradeable(token), + targetConfig, + minApprovals, + metadata + ) ) ); - address votingPowerCondition = address(new VotingPowerCondition(plugin)); - - helpers[0] = token; - helpers[1] = votingPowerCondition; + preparedSetupData.helpers = new address[](2); + preparedSetupData.helpers[0] = token; + preparedSetupData.helpers[1] = address(new VotingPowerCondition(plugin)); // Prepare permissions PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[]( - tokenAddressNotZero ? 4 : 5 + tokenSettings.addr != address(0) ? 4 : 5 ); // Set plugin permissions to be granted. @@ -200,7 +199,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { PermissionLib.Operation.GrantWithCondition, plugin, address(type(uint160).max), // ANY_ADDR - votingPowerCondition, + preparedSetupData.helpers[1], // VotingPowerCondition TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID() ); @@ -212,7 +211,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { permissionId: SET_TARGET_CONFIG_PERMISSION_ID }); - if (!tokenAddressNotZero) { + if (tokenSettings.addr == address(0)) { bytes32 tokenMintPermission = GovernanceERC20(token).MINT_PERMISSION_ID(); permissions[4] = PermissionLib.MultiTargetPermission({ @@ -224,7 +223,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { }); } - preparedSetupData.helpers = helpers; + // preparedSetupData.helpers = helpers; preparedSetupData.permissions = permissions; } diff --git a/packages/contracts/src/build-metadata.json b/packages/contracts/src/build-metadata.json index 452776cf..afa47b68 100644 --- a/packages/contracts/src/build-metadata.json +++ b/packages/contracts/src/build-metadata.json @@ -114,6 +114,12 @@ "name": "minApproval", "type": "uint256", "description": "The minimum amount of yes votes needed for the proposal advance." + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes", + "description": "The metadata that contains the information about the multisig." } ] }, @@ -127,11 +133,11 @@ "inputs": [] }, "3": { - "description": "The information required for the installation.", + "description": "The information required for the update.", "inputs": [ { "internalType": "uint256", - "name": "minApproval", + "name": "minApprovals", "type": "uint256", "description": "The minimum amount of yes votes needed for the proposal advance." }, @@ -144,16 +150,22 @@ "description": "The target contract to which actions will be forwarded to for execution." }, { - "internalType": "enum PluginUUPSUpgradeable.Operation", + "internalType": "uint8", "name": "operation", "type": "uint8", "description": "The operation type(either `call` or `delegatecall`) that will be used for execution forwarding." } ], - "internalType": "struct PluginUUPSUpgradeable.TargetConfig", + "internalType": "struct TokenVoting.TargetConfig", "name": "TargetConfig", "type": "tuple", "description": "The initial target config" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes", + "description": "The metadata that contains the information about the multisig." } ] } diff --git a/packages/contracts/src/mocks/MajorityVotingMock.sol b/packages/contracts/src/mocks/MajorityVotingMock.sol index fc279576..c82902bd 100644 --- a/packages/contracts/src/mocks/MajorityVotingMock.sol +++ b/packages/contracts/src/mocks/MajorityVotingMock.sol @@ -11,9 +11,10 @@ contract MajorityVotingMock is MajorityVotingBase { IDAO _dao, VotingSettings calldata _votingSettings, TargetConfig calldata _targetConfig, - uint256 _minApprovals + uint256 _minApprovals, + bytes calldata _metadata ) public initializer { - __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals); + __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _metadata); } function createProposalId( diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 20030465..c3798be8 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -88,6 +88,7 @@ type GlobalFixtureResult = { uninitializedPlugin: TokenVoting; defaultVotingSettings: MajorityVotingBase.VotingSettingsStruct; defaultMinApproval: BigNumber; + defaultMetadata: string; token: TestGovernanceERC20; dao: DAO; defaultTargetConfig: TargetConfig; @@ -143,6 +144,7 @@ async function globalFixture(): Promise { }; const defaultMinApproval = pctToRatio(10); + const defaultMetadata = '0x11'; // Deploy an initialized plugin proxy. const defaultTargetConfig: TargetConfig = { @@ -158,6 +160,7 @@ async function globalFixture(): Promise { token.address, defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); const deploymentTx1 = await proxyFactory.deployUUPSProxy(pluginInitData); @@ -232,6 +235,7 @@ async function globalFixture(): Promise { uninitializedPlugin, defaultVotingSettings, defaultMinApproval, + defaultMetadata, defaultTargetConfig, token, dao, @@ -273,6 +277,7 @@ describe('TokenVoting', function () { initializedPlugin, defaultVotingSettings, defaultMinApproval, + defaultMetadata, defaultTargetConfig, token, } = await loadFixture(globalFixture); @@ -284,7 +289,8 @@ describe('TokenVoting', function () { defaultVotingSettings, token.address, defaultTargetConfig, - defaultMinApproval + defaultMinApproval, + defaultMetadata ) ).to.be.revertedWithCustomError(initializedPlugin, 'AlreadyInitialized'); }); @@ -295,6 +301,7 @@ describe('TokenVoting', function () { uninitializedPlugin, defaultVotingSettings, defaultMinApproval, + defaultMetadata, defaultTargetConfig, token, } = await loadFixture(globalFixture); @@ -306,18 +313,20 @@ describe('TokenVoting', function () { defaultVotingSettings, token.address, defaultTargetConfig, - defaultMinApproval + defaultMinApproval, + defaultMetadata ) ) .to.emit(uninitializedPlugin, 'MembershipContractAnnounced') .withArgs(token.address); }); - it('sets the voting settings, token and minimal approval', async () => { + it('sets the voting settings, token, minimal approval and metadata', async () => { const { dao, uninitializedPlugin: plugin, defaultTargetConfig, + defaultMetadata, token, } = await loadFixture(globalFixture); @@ -347,7 +356,8 @@ describe('TokenVoting', function () { votingSettings, token.address, defaultTargetConfig, - minApproval + minApproval, + defaultMetadata ); // Check that the voting settings have been set. @@ -368,6 +378,9 @@ describe('TokenVoting', function () { // Check the minimal approval has been set. expect(await plugin.minApproval()).to.equal(minApproval); + + // Check the metadata has been set. + expect(await plugin.getMetadata()).to.equal(defaultMetadata); }); }); 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 1c9b4f82..cc67785b 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -62,7 +62,9 @@ type FixtureResult = { defaultTargetConfig: TargetConfig; defaultMintSettings: GovernanceERC20.MintSettingsStruct; defaultMinApproval: BigNumber; + defaultMetadata: string; updateMinApproval: BigNumber; + updateMetadata: string; updateTargetConfig: TargetConfig; prepareInstallationInputs: string; prepareUninstallationInputs: string; @@ -110,6 +112,8 @@ async function fixture(): Promise { operation: Op.call, }; + const defaultMetadata: string = '0x11'; + const defaultVotingSettings: MajorityVotingBase.VotingSettingsStruct = { votingMode: VotingMode.EarlyExecution, supportThreshold: pctToRatio(50), @@ -131,6 +135,7 @@ async function fixture(): Promise { Object.values(defaultMintSettings), Object.values(defaultTargetConfig), defaultMinApproval, + defaultMetadata, ] ); @@ -147,13 +152,14 @@ async function fixture(): Promise { target: pluginSetup.address, operation: Op.call, }; + const updateMetadata: string = '0x11'; // Provide update inputs const prepareUpdateBuild3Inputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareUpdate[3].inputs ), - [updateMinApproval, updateTargetConfig] + [updateMinApproval, updateTargetConfig, updateMetadata] ); return { @@ -166,9 +172,11 @@ async function fixture(): Promise { defaultTokenSettings, defaultMintSettings, defaultMinApproval, + defaultMetadata, defaultTargetConfig, updateMinApproval, updateTargetConfig, + updateMetadata, prepareInstallationInputs, prepareUninstallationInputs, prepareUpdateBuild3Inputs, @@ -199,18 +207,17 @@ describe('TokenVotingSetup', function () { describe('prepareInstallation', async () => { it('fails if data is empty, or not of minimum length', async () => { - const {pluginSetup, dao, prepareInstallationInputs} = await loadFixture( - fixture - ); + const {pluginSetup, dao, prepareInstallationInputs, defaultMetadata} = + await loadFixture(fixture); // Try calling `prepareInstallation` without input data. await expect(pluginSetup.prepareInstallation(dao.address, [])).to.be .reverted; - // Try calling `prepareInstallation` without input data of wrong length. + // Try calling `prepareInstallation` with input data of wrong length. const trimmedData = prepareInstallationInputs.substring( 0, - prepareInstallationInputs.length - 2 + prepareInstallationInputs.length - 100 ); await expect(pluginSetup.prepareInstallation(dao.address, trimmedData)).to .be.reverted; @@ -230,6 +237,7 @@ describe('TokenVotingSetup', function () { defaultTokenSettings, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const receivers: string[] = [AddressZero]; @@ -244,6 +252,7 @@ describe('TokenVotingSetup', function () { {receivers, amounts}, defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -276,6 +285,7 @@ describe('TokenVotingSetup', function () { defaultMintSettings, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const data = abiCoder.encode( @@ -288,6 +298,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -304,6 +315,7 @@ describe('TokenVotingSetup', function () { defaultMintSettings, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const data = abiCoder.encode( @@ -316,6 +328,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -334,6 +347,7 @@ describe('TokenVotingSetup', function () { erc20, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const nonce = await ethers.provider.getTransactionCount( @@ -367,6 +381,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -427,6 +442,7 @@ describe('TokenVotingSetup', function () { erc20, defaultTargetConfig, defaultMinApproval, + defaultMetadata, } = await loadFixture(fixture); const nonce = await ethers.provider.getTransactionCount( @@ -447,6 +463,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -506,6 +523,7 @@ describe('TokenVotingSetup', function () { defaultMintSettings, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const governanceERC20 = await new GovernanceERC20__factory( @@ -536,6 +554,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -680,6 +699,7 @@ describe('TokenVotingSetup', function () { defaultMintSettings, defaultMinApproval, defaultTargetConfig, + defaultMetadata, } = await loadFixture(fixture); const daoAddress = dao.address; @@ -694,6 +714,7 @@ describe('TokenVotingSetup', function () { Object.values(defaultMintSettings), defaultTargetConfig, defaultMinApproval, + defaultMetadata, ] ); @@ -768,8 +789,6 @@ describe('TokenVotingSetup', function () { dao, prepareInstallationInputs, prepareUpdateBuild3Inputs, - updateMinApproval, - updateTargetConfig, } = await loadFixture(fixture); const nonce = await ethers.provider.getTransactionCount( @@ -849,8 +868,6 @@ describe('TokenVotingSetup', function () { dao, prepareInstallationInputs, prepareUpdateBuild3Inputs, - updateMinApproval, - updateTargetConfig, } = await loadFixture(fixture); const nonce = await ethers.provider.getTransactionCount( diff --git a/packages/contracts/test/10_unit-testing/base/11_majority-voting.ts b/packages/contracts/test/10_unit-testing/base/11_majority-voting.ts index 589c6e9b..b110112b 100644 --- a/packages/contracts/test/10_unit-testing/base/11_majority-voting.ts +++ b/packages/contracts/test/10_unit-testing/base/11_majority-voting.ts @@ -41,6 +41,7 @@ describe('MajorityVotingMock', function () { let votingSettings: MajorityVotingBase.VotingSettingsStruct; let minApproval: BigNumber; let targetConfig: TargetConfig; + let metadata: string; before(async () => { signers = await ethers.getSigners(); @@ -64,6 +65,8 @@ describe('MajorityVotingMock', function () { operation: Operation.call, }; + metadata = '0x11'; + const pluginImplementation = await new MajorityVotingMock__factory( signers[0] ).deploy(); @@ -93,7 +96,8 @@ describe('MajorityVotingMock', function () { dao.address, votingSettings, targetConfig, - minApproval + minApproval, + metadata ); await expect( @@ -101,7 +105,8 @@ describe('MajorityVotingMock', function () { dao.address, votingSettings, targetConfig, - minApproval + minApproval, + metadata ) ).to.be.revertedWith('Initializable: contract is already initialized'); }); @@ -171,7 +176,8 @@ describe('MajorityVotingMock', function () { dao.address, votingSettings, targetConfig, - minApproval + minApproval, + metadata ); }); @@ -253,7 +259,8 @@ describe('MajorityVotingMock', function () { dao.address, votingSettings, targetConfig, - minApproval + minApproval, + metadata ); }); @@ -292,7 +299,8 @@ describe('MajorityVotingMock', function () { dao.address, votingSettings, targetConfig, - minApproval + minApproval, + metadata ); await dao.grant( 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 dd1ca02a..8f0f14cf 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -64,6 +64,7 @@ type FixtureResult = { }; defaultMintSettings: GovernanceERC20.MintSettingsStruct; defaultMinApproval: BigNumber; + defaultMetadata: string; defaultTargetConfig: TargetConfig; prepareInstallationInputs: string; prepareInstallData: any; @@ -152,6 +153,8 @@ async function fixture(): Promise { amounts: [], }; + const defaultMetadata: string = '0x11'; + // Provide uninstallation inputs const prepareInstallationInputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( @@ -163,6 +166,7 @@ async function fixture(): Promise { Object.values(defaultMintSettings), Object.values(defaultTargetConfig), defaultMinApproval, + defaultMetadata, ] ); @@ -172,9 +176,14 @@ async function fixture(): Promise { mintSettings: Object.values(defaultMintSettings), targetConfig: Object.values(defaultTargetConfig), defaultMinApproval, + defaultMetadata, }; - const prepareUpdateData = [defaultMinApproval, defaultTargetConfig]; + const prepareUpdateData = [ + defaultMinApproval, + defaultTargetConfig, + defaultMetadata, + ]; // Provide update inputs // const prepareUpdateBuild3Data = [defaultMinApproval]; return { @@ -190,6 +199,7 @@ async function fixture(): Promise { defaultTokenSettings, defaultMintSettings, defaultMinApproval, + defaultMetadata, defaultTargetConfig, prepareInstallationInputs, prepareInstallData, @@ -275,6 +285,7 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio defaultVotingSettings, pluginSetupRefLatestBuild, defaultMinApproval, + defaultMetadata, defaultTargetConfig, } = await loadFixture(fixture); @@ -303,6 +314,7 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio mintSettings: [[alice.address], ['1000']], defaultTargetConfig, defaultMinApproval, + defaultMetadata, }; const prepareInstallInputType = getNamedTypesFromMetadata( diff --git a/packages/contracts/test/30_regression-testing/31_upgradeability.ts b/packages/contracts/test/30_regression-testing/31_upgradeability.ts index c22059a9..df3a2b99 100644 --- a/packages/contracts/test/30_regression-testing/31_upgradeability.ts +++ b/packages/contracts/test/30_regression-testing/31_upgradeability.ts @@ -49,6 +49,7 @@ describe('Upgrades', () => { defaultInitData.token.address, defaultInitData.targetConfig, defaultInitData.minApproval, + defaultInitData.metadata, ], INITIALIZE_SIGNATURE, currentContractFactory, @@ -83,6 +84,7 @@ describe('Upgrades', () => { defaultInitData.token.address, defaultInitData.targetConfig, defaultInitData.minApproval, + defaultInitData.metadata, ], ]; @@ -129,6 +131,9 @@ describe('Upgrades', () => { expect(await newTokenVoting.minApproval()).to.equal( defaultInitData.minApproval ); + expect(await newTokenVoting.getMetadata()).to.equal( + defaultInitData.metadata + ); expect(await newTokenVoting.getTargetConfig()).to.deep.equal([ defaultInitData.targetConfig.target, defaultInitData.targetConfig.operation, @@ -142,7 +147,8 @@ describe('Upgrades', () => { defaultInitData.votingSettings, defaultInitData.token.address, defaultInitData.targetConfig, - defaultInitData.minApproval + defaultInitData.minApproval, + defaultInitData.metadata ) ).to.be.revertedWithCustomError(proxy, 'AlreadyInitialized'); }); @@ -173,6 +179,7 @@ describe('Upgrades', () => { defaultInitData.token.address, defaultInitData.targetConfig, defaultInitData.minApproval, + defaultInitData.metadata, ], ]; @@ -218,6 +225,9 @@ describe('Upgrades', () => { expect(await newTokenVoting.minApproval()).to.equal( defaultInitData.minApproval ); + expect(await newTokenVoting.getMetadata()).to.equal( + defaultInitData.metadata + ); expect(await newTokenVoting.getTargetConfig()).to.deep.equal([ defaultInitData.targetConfig.target, defaultInitData.targetConfig.operation, @@ -231,7 +241,8 @@ describe('Upgrades', () => { defaultInitData.votingSettings, defaultInitData.token.address, defaultInitData.targetConfig, - defaultInitData.minApproval + defaultInitData.minApproval, + defaultInitData.metadata ) ).to.be.revertedWithCustomError(proxy, 'AlreadyInitialized'); }); @@ -248,6 +259,7 @@ type FixtureResult = { token: TestGovernanceERC20; minApproval: BigNumber; targetConfig: TargetConfig; + metadata: string; }; encodedParamsForUpgrade: string; }; @@ -286,15 +298,17 @@ async function fixture(): Promise { target: dao.address, operation: Operation.call, }, + metadata: '0x11', }; // initial data is minApproval and targetConfig const encodedParamsForUpgrade = ethers.utils.defaultAbiCoder.encode( - ['uint256', 'address', 'uint8'], + ['uint256', 'address', 'uint8', 'bytes'], [ defaultInitData.minApproval, defaultInitData.targetConfig.target, defaultInitData.targetConfig.operation, + defaultInitData.metadata, ] ); diff --git a/packages/contracts/test/test-utils/token-voting-constants.ts b/packages/contracts/test/test-utils/token-voting-constants.ts index 3ce3fe0d..cf9f098f 100644 --- a/packages/contracts/test/test-utils/token-voting-constants.ts +++ b/packages/contracts/test/test-utils/token-voting-constants.ts @@ -37,7 +37,7 @@ export const VOTING_EVENTS = { export const ANY_ADDR = '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'; export const INITIALIZE_SIGNATURE = - 'initialize(address,(uint8,uint32,uint32,uint64,uint256),address,(address,uint8),uint256)'; + 'initialize(address,(uint8,uint32,uint32,uint64,uint256),address,(address,uint8),uint256,bytes)'; export const CREATE_PROPOSAL_SIGNATURE = 'createProposal(bytes,(address,uint256,bytes)[],uint256,uint64,uint64,uint8,bool)'; From f316675bf106a49e165c079a630f1501b6249882 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:26:33 +0400 Subject: [PATCH 2/9] Update packages/contracts/src/MajorityVotingBase.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/MajorityVotingBase.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/src/MajorityVotingBase.sol b/packages/contracts/src/MajorityVotingBase.sol index aa7c6493..049e3bc0 100644 --- a/packages/contracts/src/MajorityVotingBase.sol +++ b/packages/contracts/src/MajorityVotingBase.sol @@ -303,13 +303,13 @@ abstract contract MajorityVotingBase is VotingSettings calldata _votingSettings, TargetConfig calldata _targetConfig, uint256 _minApprovals, - bytes calldata _metadata + bytes calldata _pluginMetadata ) internal onlyInitializing { __PluginUUPSUpgradeable_init(_dao); _updateVotingSettings(_votingSettings); _updateMinApprovals(_minApprovals); _setTargetConfig(_targetConfig); - _updateMetadata(_metadata); + _updateMetadata(_pluginMetadata); } /// @notice Checks if this or the parent contract supports an interface by its ID. From ccfa5d92d815d1aed17ae242f839e541616f079b Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:26:40 +0400 Subject: [PATCH 3/9] Update packages/contracts/src/TokenVoting.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVoting.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/src/TokenVoting.sol b/packages/contracts/src/TokenVoting.sol index 6cbe0580..1bfe0ccf 100644 --- a/packages/contracts/src/TokenVoting.sol +++ b/packages/contracts/src/TokenVoting.sol @@ -47,7 +47,7 @@ contract TokenVoting is IMembership, MajorityVotingBase { IVotesUpgradeable _token, TargetConfig calldata _targetConfig, uint256 _minApprovals, - bytes calldata _metadata + bytes calldata _pluginMetadata ) external onlyCallAtInitialization reinitializer(2) { __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _metadata); From d49093c05dcb3fa305001ea1279545aa88f6d408 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:27:13 +0400 Subject: [PATCH 4/9] Update packages/contracts/src/TokenVoting.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVoting.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/src/TokenVoting.sol b/packages/contracts/src/TokenVoting.sol index 1bfe0ccf..295f0d8b 100644 --- a/packages/contracts/src/TokenVoting.sol +++ b/packages/contracts/src/TokenVoting.sol @@ -49,7 +49,7 @@ contract TokenVoting is IMembership, MajorityVotingBase { uint256 _minApprovals, bytes calldata _pluginMetadata ) external onlyCallAtInitialization reinitializer(2) { - __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _metadata); + __MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _pluginMetadata); votingToken = _token; From ab1af364d68b117d2a15dfa1e5b7aaf94ff6f285 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:27:21 +0400 Subject: [PATCH 5/9] Update packages/contracts/src/TokenVoting.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVoting.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/src/TokenVoting.sol b/packages/contracts/src/TokenVoting.sol index 295f0d8b..3057970b 100644 --- a/packages/contracts/src/TokenVoting.sol +++ b/packages/contracts/src/TokenVoting.sol @@ -62,14 +62,14 @@ contract TokenVoting is IMembership, MajorityVotingBase { /// @param _initData The initialization data to be passed to via `upgradeToAndCall` (see [ERC-1967](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Upgrade)). function initializeFrom(uint16 _fromBuild, bytes calldata _initData) external reinitializer(2) { if (_fromBuild < 3) { - (uint256 minApprovals, TargetConfig memory targetConfig, bytes memory metadata) = abi + (uint256 minApprovals, TargetConfig memory targetConfig, bytes memory pluginMetadata) = abi .decode(_initData, (uint256, TargetConfig, bytes)); _updateMinApprovals(minApprovals); _setTargetConfig(targetConfig); - _updateMetadata(metadata); + _updateMetadata(pluginMetadata); } } From 3176cd46e47039752c6d0c02eb9ecf6bd2d477f7 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:27:54 +0400 Subject: [PATCH 6/9] Update packages/contracts/src/TokenVotingSetup.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVotingSetup.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/src/TokenVotingSetup.sol b/packages/contracts/src/TokenVotingSetup.sol index 2fc432e8..28d4490f 100644 --- a/packages/contracts/src/TokenVotingSetup.sol +++ b/packages/contracts/src/TokenVotingSetup.sol @@ -105,7 +105,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { GovernanceERC20.MintSettings memory mintSettings, PluginUUPSUpgradeable.TargetConfig memory targetConfig, uint256 minApprovals, - bytes memory metadata + bytes memory pluginMetadata ) = abi.decode( _data, ( From 3de9e37efe0c7beac4f00981e2de184602118f69 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:28:01 +0400 Subject: [PATCH 7/9] Update packages/contracts/src/TokenVotingSetup.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVotingSetup.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/src/TokenVotingSetup.sol b/packages/contracts/src/TokenVotingSetup.sol index 28d4490f..7a15422b 100644 --- a/packages/contracts/src/TokenVotingSetup.sol +++ b/packages/contracts/src/TokenVotingSetup.sol @@ -161,7 +161,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { IVotesUpgradeable(token), targetConfig, minApprovals, - metadata + pluginMetadata ) ) ); From 9799f4e45e986c3a84583acc26975f9f2f407eb8 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:28:12 +0400 Subject: [PATCH 8/9] Update packages/contracts/src/TokenVotingSetup.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> --- packages/contracts/src/TokenVotingSetup.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts/src/TokenVotingSetup.sol b/packages/contracts/src/TokenVotingSetup.sol index 7a15422b..c5945623 100644 --- a/packages/contracts/src/TokenVotingSetup.sol +++ b/packages/contracts/src/TokenVotingSetup.sol @@ -223,7 +223,6 @@ contract TokenVotingSetup is PluginUpgradeableSetup { }); } - // preparedSetupData.helpers = helpers; preparedSetupData.permissions = permissions; } From 7b3d080e24f8c801c8f0513db373c37d11d63180 Mon Sep 17 00:00:00 2001 From: Giorgi Lagidze Date: Tue, 8 Oct 2024 13:52:31 +0400 Subject: [PATCH 9/9] permission added --- packages/contracts/src/TokenVotingSetup.sol | 49 ++++++++++++---- .../test/10_unit-testing/12_plugin-setup.ts | 58 ++++++++++++++++--- .../test/test-utils/token-voting-constants.ts | 4 ++ 3 files changed, 93 insertions(+), 18 deletions(-) diff --git a/packages/contracts/src/TokenVotingSetup.sol b/packages/contracts/src/TokenVotingSetup.sol index c5945623..e34b4e09 100644 --- a/packages/contracts/src/TokenVotingSetup.sol +++ b/packages/contracts/src/TokenVotingSetup.sol @@ -44,6 +44,9 @@ contract TokenVotingSetup is PluginUpgradeableSetup { bytes32 public constant SET_TARGET_CONFIG_PERMISSION_ID = keccak256("SET_TARGET_CONFIG_PERMISSION"); + /// @notice The ID of the permission required to call the `updateMetadata` function. + bytes32 public constant UPDATE_METADATA_PERMISSION_ID = keccak256("UPDATE_METADATA_PERMISSION"); + /// @notice The ID of the permission required to call the `upgradeToAndCall` function. bytes32 internal constant UPGRADE_PLUGIN_PERMISSION_ID = keccak256("UPGRADE_PLUGIN_PERMISSION"); @@ -173,7 +176,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { // Prepare permissions PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[]( - tokenSettings.addr != address(0) ? 4 : 5 + tokenSettings.addr != address(0) ? 5 : 6 ); // Set plugin permissions to be granted. @@ -211,10 +214,18 @@ contract TokenVotingSetup is PluginUpgradeableSetup { permissionId: SET_TARGET_CONFIG_PERMISSION_ID }); + permissions[4] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: UPDATE_METADATA_PERMISSION_ID + }); + if (tokenSettings.addr == address(0)) { bytes32 tokenMintPermission = GovernanceERC20(token).MINT_PERMISSION_ID(); - permissions[4] = PermissionLib.MultiTargetPermission({ + permissions[5] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Grant, where: token, who: _dao, @@ -241,7 +252,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { address votingPowerCondition = address(new VotingPowerCondition(_payload.plugin)); PermissionLib.MultiTargetPermission[] - memory permissions = new PermissionLib.MultiTargetPermission[](3); + memory permissions = new PermissionLib.MultiTargetPermission[](4); permissions[0] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, @@ -267,6 +278,14 @@ contract TokenVotingSetup is PluginUpgradeableSetup { permissionId: SET_TARGET_CONFIG_PERMISSION_ID }); + permissions[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: _payload.plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: UPDATE_METADATA_PERMISSION_ID + }); + preparedSetupData.permissions = permissions; preparedSetupData.helpers = new address[](1); preparedSetupData.helpers[0] = votingPowerCondition; @@ -281,7 +300,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup { SetupPayload calldata _payload ) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) { // Prepare permissions. - permissions = new PermissionLib.MultiTargetPermission[](4); + permissions = new PermissionLib.MultiTargetPermission[](5); // Set permissions to be Revoked. permissions[0] = PermissionLib.MultiTargetPermission({ @@ -308,13 +327,21 @@ contract TokenVotingSetup is PluginUpgradeableSetup { permissionId: SET_TARGET_CONFIG_PERMISSION_ID }); - permissions[3] = PermissionLib.MultiTargetPermission( - PermissionLib.Operation.Revoke, - _payload.plugin, - address(type(uint160).max), // ANY_ADDR - PermissionLib.NO_CONDITION, - TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID() - ); + permissions[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _payload.plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: UPDATE_METADATA_PERMISSION_ID + }); + + permissions[4] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _payload.plugin, + who: address(type(uint160).max), // ANY_ADDR + condition: PermissionLib.NO_CONDITION, + permissionId: TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID() + }); } /// @notice Unsatisfiably determines if the token is an IVotes interface. 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 cc67785b..f3f1c42d 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -19,6 +19,7 @@ import { MINT_PERMISSION_ID, SET_TARGET_CONFIG_PERMISSION_ID, TargetConfig, + UPDATE_METADATA_PERMISSION_ID, UPDATE_VOTING_SETTINGS_PERMISSION_ID, } from '../test-utils/token-voting-constants'; import {Operation as Op} from '../test-utils/token-voting-constants'; @@ -399,7 +400,7 @@ describe('TokenVotingSetup', function () { anticipatedWrappedTokenAddress, anticipatedCondition, ]); - expect(permissions.length).to.be.equal(4); + expect(permissions.length).to.be.equal(5); expect(permissions).to.deep.equal([ [ Operation.Grant, @@ -429,6 +430,13 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + [ + Operation.Grant, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], ]); }); @@ -572,7 +580,7 @@ describe('TokenVotingSetup', function () { governanceERC20.address, anticipatedCondition, ]); - expect(permissions.length).to.be.equal(4); + expect(permissions.length).to.be.equal(5); expect(permissions).to.deep.equal([ [ Operation.Grant, @@ -602,6 +610,13 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + [ + Operation.Grant, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], ]); }); @@ -649,7 +664,7 @@ describe('TokenVotingSetup', function () { anticipatedTokenAddress, anticipatedCondition, ]); - expect(permissions.length).to.be.equal(5); + expect(permissions.length).to.be.equal(6); expect(permissions).to.deep.equal([ [ Operation.Grant, @@ -679,6 +694,13 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + [ + Operation.Grant, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], [ Operation.Grant, anticipatedTokenAddress, @@ -836,7 +858,7 @@ describe('TokenVotingSetup', function () { }); expect(helpers).to.deep.equal([anticipatedCondition]); - expect(permissions.length).to.be.eql(3); + expect(permissions.length).to.be.eql(4); expect(permissions).to.deep.equal([ [ Operation.Revoke, @@ -859,6 +881,14 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + + [ + Operation.Grant, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], ]); }); @@ -914,7 +944,7 @@ describe('TokenVotingSetup', function () { ) ); expect(helpers).to.be.eql([anticipatedCondition]); - expect(permissions.length).to.be.eql(3); + expect(permissions.length).to.be.eql(4); expect(permissions).to.deep.equal([ [ Operation.Revoke, @@ -937,6 +967,13 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + [ + Operation.Grant, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], ]); }); @@ -1029,6 +1066,13 @@ describe('TokenVotingSetup', function () { AddressZero, SET_TARGET_CONFIG_PERMISSION_ID, ], + [ + Operation.Revoke, + plugin, + dao.address, + AddressZero, + UPDATE_METADATA_PERMISSION_ID, + ], [ Operation.Revoke, plugin, @@ -1038,7 +1082,7 @@ describe('TokenVotingSetup', function () { ], ]; - expect(permissions1.length).to.be.equal(4); + expect(permissions1.length).to.be.equal(5); expect(permissions1).to.deep.equal(essentialPermissions); const permissions2 = await pluginSetup.callStatic.prepareUninstallation( @@ -1050,7 +1094,7 @@ describe('TokenVotingSetup', function () { } ); - expect(permissions2.length).to.be.equal(4); + expect(permissions2.length).to.be.equal(5); expect(permissions2).to.deep.equal(essentialPermissions); }); }); diff --git a/packages/contracts/test/test-utils/token-voting-constants.ts b/packages/contracts/test/test-utils/token-voting-constants.ts index cf9f098f..d39f3ba9 100644 --- a/packages/contracts/test/test-utils/token-voting-constants.ts +++ b/packages/contracts/test/test-utils/token-voting-constants.ts @@ -29,6 +29,10 @@ export const SET_TARGET_CONFIG_PERMISSION_ID = ethers.utils.id( 'SET_TARGET_CONFIG_PERMISSION' ); +export const UPDATE_METADATA_PERMISSION_ID = ethers.utils.id( + 'UPDATE_METADATA_PERMISSION' +); + export const VOTING_EVENTS = { VOTING_SETTINGS_UPDATED: 'VotingSettingsUpdated', VOTE_CAST: 'VoteCast',