diff --git a/packages/contracts/src/Admin.sol b/packages/contracts/src/Admin.sol index 7fc65e34..09d9c65e 100644 --- a/packages/contracts/src/Admin.sol +++ b/packages/contracts/src/Admin.sol @@ -10,6 +10,8 @@ import {IMembership} from "@aragon/osx-commons-contracts/src/plugin/extensions/m import {ProposalUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol"; import {PluginCloneable} from "@aragon/osx-commons-contracts/src/plugin/PluginCloneable.sol"; 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"; /// @title Admin /// @author Aragon X - 2022-2023 @@ -20,8 +22,7 @@ contract Admin is IMembership, PluginCloneable, ProposalUpgradeable { using SafeCastUpgradeable for uint256; /// @notice The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. - bytes4 internal constant ADMIN_INTERFACE_ID = - this.initialize.selector ^ this.executeProposal.selector; + bytes4 internal constant ADMIN_INTERFACE_ID = this.executeProposal.selector; /// @notice The ID of the permission required to call the `executeProposal` function. bytes32 public constant EXECUTE_PROPOSAL_PERMISSION_ID = @@ -30,9 +31,11 @@ contract Admin is IMembership, PluginCloneable, ProposalUpgradeable { /// @notice Initializes the contract. /// @param _dao The associated DAO. /// @dev This method is required to support [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167). - function initialize(IDAO _dao) external initializer { + function initialize(IDAO _dao, TargetConfig calldata _targetConfig) external initializer { __PluginCloneable_init(_dao); + _setTargetConfig(_targetConfig); + emit MembershipContractAnnounced({definingContract: address(_dao)}); } @@ -59,6 +62,34 @@ contract Admin is IMembership, PluginCloneable, ProposalUpgradeable { }); } + /// @inheritdoc IProposal + function customProposalParamsABI() external pure override returns (string memory) { + return "(uint256 allowFailureMap)"; + } + + /// @inheritdoc IProposal + function createProposal( + bytes calldata _metadata, + Action[] calldata _actions, + uint64, + uint64, + bytes memory _data + ) public override returns (uint256 proposalId) { + uint256 allowFailureMap; + + if (_data.length > 0) { + allowFailureMap = abi.decode(_data, (uint256)); + } + + // Uses public function for permission check. + proposalId = executeProposal(_metadata, _actions, allowFailureMap); + } + + /// @inheritdoc IProposal + function canExecute(uint256) public view virtual override returns (bool) { + return true; + } + /// @notice Creates and executes a new proposal. /// @param _metadata The metadata of the proposal. /// @param _actions The actions to be executed. @@ -67,19 +98,33 @@ contract Admin is IMembership, PluginCloneable, ProposalUpgradeable { // of 0 requires every action to not revert. function executeProposal( bytes calldata _metadata, - IDAO.Action[] calldata _actions, + Action[] calldata _actions, uint256 _allowFailureMap - ) external auth(EXECUTE_PROPOSAL_PERMISSION_ID) { - uint64 currentTimestamp64 = block.timestamp.toUint64(); - - uint256 proposalId = _createProposal({ - _creator: _msgSender(), - _metadata: _metadata, - _startDate: currentTimestamp64, - _endDate: currentTimestamp64, - _actions: _actions, - _allowFailureMap: _allowFailureMap - }); - _executeProposal(dao(), proposalId, _actions, _allowFailureMap); + ) public auth(EXECUTE_PROPOSAL_PERMISSION_ID) returns (uint256 proposalId) { + uint64 currentTimestamp = block.timestamp.toUint64(); + + proposalId = _createProposalId(keccak256(abi.encode(_actions, _metadata))); + + TargetConfig memory targetConfig = getTargetConfig(); + + _execute( + targetConfig.target, + bytes32(proposalId), + _actions, + _allowFailureMap, + targetConfig.operation + ); + + emit ProposalCreated( + proposalId, + _msgSender(), + currentTimestamp, + currentTimestamp, + _metadata, + _actions, + _allowFailureMap + ); + + emit ProposalExecuted(proposalId); } } diff --git a/packages/contracts/src/AdminSetup.sol b/packages/contracts/src/AdminSetup.sol index 7d09c15c..a6c34fb8 100644 --- a/packages/contracts/src/AdminSetup.sol +++ b/packages/contracts/src/AdminSetup.sol @@ -7,6 +7,7 @@ import {PluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/Plugin import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol"; import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; +import {IPlugin} from "@aragon/osx-commons-contracts/src/plugin/IPlugin.sol"; import {Admin} from "./Admin.sol"; @@ -18,10 +19,13 @@ import {Admin} from "./Admin.sol"; contract AdminSetup is PluginSetup { using ProxyLib for address; - // TODO This permission identifier has to be moved inside `PermissionLib` as per task OS-954. /// @notice The ID of the permission required to call the `execute` function. bytes32 internal constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); + /// @notice The ID of the permission required to call the `executeProposal` function. + bytes32 public constant EXECUTE_PROPOSAL_PERMISSION_ID = + keccak256("EXECUTE_PROPOSAL_PERMISSION"); + /// @notice Thrown if the admin address is zero. /// @param admin The admin address. error AdminAddressInvalid(address admin); @@ -35,14 +39,17 @@ contract AdminSetup is PluginSetup { bytes calldata _data ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { // Decode `_data` to extract the params needed for cloning and initializing the `Admin` plugin. - address admin = abi.decode(_data, (address)); + (address admin, IPlugin.TargetConfig memory targetConfig) = abi.decode( + _data, + (address, IPlugin.TargetConfig) + ); if (admin == address(0)) { revert AdminAddressInvalid({admin: admin}); } // Clone and initialize the plugin contract. - bytes memory initData = abi.encodeCall(Admin.initialize, (IDAO(_dao))); + bytes memory initData = abi.encodeCall(Admin.initialize, (IDAO(_dao), targetConfig)); plugin = IMPLEMENTATION.deployMinimalProxy(initData); // Prepare permissions @@ -55,7 +62,7 @@ contract AdminSetup is PluginSetup { where: plugin, who: admin, condition: PermissionLib.NO_CONDITION, - permissionId: Admin(plugin).EXECUTE_PROPOSAL_PERMISSION_ID() + permissionId: EXECUTE_PROPOSAL_PERMISSION_ID }); // Grant `EXECUTE_PERMISSION` on the DAO to the plugin. diff --git a/packages/contracts/src/build-metadata.json b/packages/contracts/src/build-metadata.json index c479f4b5..5c53a36b 100644 --- a/packages/contracts/src/build-metadata.json +++ b/packages/contracts/src/build-metadata.json @@ -10,6 +10,26 @@ "name": "admin", "type": "address", "description": "The address of the admin account receiving the `EXECUTE_PERMISSION_ID` permission." + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address", + "description": "The target contract to which actions will be forwarded to for execution." + }, + { + "internalType": "uint8", + "name": "operation", + "type": "uint8", + "description": "The operation type(either `call` or `delegatecall`) that will be used for execution forwarding." + } + ], + "internalType": "struct TokenVoting.TargetConfig", + "name": "TargetConfig", + "type": "tuple", + "description": "The initial target config" } ] }, diff --git a/packages/contracts/src/mocks/CustomExecutorMock.sol b/packages/contracts/src/mocks/CustomExecutorMock.sol new file mode 100644 index 00000000..31986842 --- /dev/null +++ b/packages/contracts/src/mocks/CustomExecutorMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {Action} from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol"; + +/// @dev DO NOT USE IN PRODUCTION! +contract CustomExecutorMock { + error FailedCustom(); + + event ExecutedCustom(); + + function execute( + bytes32 callId, + Action[] memory, + uint256 + ) external returns (bytes[] memory execResults, uint256 failureMap) { + (execResults, failureMap); + + if (callId == bytes32(0)) { + revert FailedCustom(); + } else { + emit ExecutedCustom(); + } + } +} diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 0cf9b5c4..dc52294a 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -3,6 +3,7 @@ import {PLUGIN_CONTRACT_NAME} from '../../plugin-settings'; import { Admin, Admin__factory, + CustomExecutorMock__factory, IERC165Upgradeable__factory, IMembership__factory, IPlugin__factory, @@ -15,11 +16,13 @@ import {ProposalCreatedEvent} from '../../typechain/src/Admin'; import { ADMIN_INTERFACE, EXECUTE_PROPOSAL_PERMISSION_ID, + Operation, + SET_TARGET_CONFIG_PERMISSION_ID, + TargetConfig, } from '../admin-constants'; import { findEvent, findEventTopicLog, - proposalIdToBytes32, getInterfaceId, DAO_PERMISSIONS, } from '@aragon/osx-commons-sdk'; @@ -27,20 +30,58 @@ import {DAO, DAOEvents, DAOStructs} 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 {defaultAbiCoder, keccak256} from 'ethers/lib/utils'; import {ethers} from 'hardhat'; +let chainId: number; + +async function createProposalId( + pluginAddress: string, + actions: DAOStructs.ActionStruct[], + metadata: string +): Promise { + const blockNumber = (await ethers.provider.getBlock('latest')).number; + const salt = keccak256( + defaultAbiCoder.encode( + ['tuple(address to,uint256 value,bytes data)[]', 'bytes'], + [actions, metadata] + ) + ); + return BigNumber.from( + keccak256( + defaultAbiCoder.encode( + ['uint256', 'uint256', 'address', 'bytes32'], + [chainId, blockNumber + 1, pluginAddress, salt] + ) + ) + ); +} + describe(PLUGIN_CONTRACT_NAME, function () { + before(async () => { + chainId = (await ethers.provider.getNetwork()).chainId; + }); + describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { - const {initializedPlugin: plugin, dao} = await loadFixture(fixture); - await expect(plugin.initialize(dao.address)).to.be.revertedWith( - 'Initializable: contract is already initialized' - ); + const { + initializedPlugin: plugin, + dao, + targetConfig, + } = await loadFixture(fixture); + await expect( + plugin.initialize(dao.address, targetConfig) + ).to.be.revertedWith('Initializable: contract is already initialized'); }); it('emits the `MembershipContractAnnounced` event', async () => { - const {uninitializedPlugin: plugin, dao} = await loadFixture(fixture); - await expect(plugin.initialize(dao.address)) + const { + uninitializedPlugin: plugin, + dao, + targetConfig, + } = await loadFixture(fixture); + await expect(plugin.initialize(dao.address, targetConfig)) .to.emit( plugin, plugin.interface.getEvent('MembershipContractAnnounced').name @@ -113,7 +154,7 @@ describe(PLUGIN_CONTRACT_NAME, function () { }); describe('execute proposal: ', async () => { - it('reverts when calling `executeProposal()` if `EXECUTE_PROPOSAL_PERMISSION_ID` is not granted to the admin address', async () => { + it('reverts when calling `execute()` if `EXECUTE_PROPOSAL_PERMISSION_ID` is not granted to the admin address', async () => { const { alice, initializedPlugin: plugin, @@ -132,7 +173,7 @@ describe(PLUGIN_CONTRACT_NAME, function () { ) ).to.be.false; - // Expect Alice's `executeProposal` call to be reverted because she hasn't `EXECUTE_PROPOSAL_PERMISSION_ID` on the Admin plugin + // Expect Alice's `execute` call to be reverted because she hasn't `EXECUTE_PROPOSAL_PERMISSION_ID` on the Admin plugin await expect( plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) ) @@ -145,7 +186,7 @@ describe(PLUGIN_CONTRACT_NAME, function () { ); }); - it('reverts when calling `executeProposal()` if the `EXECUTE_PERMISSION_ID` on the DAO is not granted to the plugin address', async () => { + it('reverts when calling `execute()` if the `EXECUTE_PERMISSION_ID` on the DAO is not granted to the plugin address', async () => { const { alice, initializedPlugin: plugin, @@ -171,7 +212,7 @@ describe(PLUGIN_CONTRACT_NAME, function () { ) ).to.be.false; - // Expect Alice's the `executeProposal` call to be reverted because the Admin plugin hasn't `EXECUTE_PERMISSION_ID` on the DAO + // Expect Alice's the `execute` call to be reverted because the Admin plugin hasn't `EXECUTE_PERMISSION_ID` on the DAO await expect( plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) ) @@ -205,7 +246,11 @@ describe(PLUGIN_CONTRACT_NAME, function () { DAO_PERMISSIONS.EXECUTE_PERMISSION_ID ); - const currentExpectedProposalId = 0; + const currentExpectedProposalId = await createProposalId( + plugin.address, + dummyActions, + dummyMetadata + ); const allowFailureMap = 1; @@ -248,7 +293,11 @@ describe(PLUGIN_CONTRACT_NAME, function () { DAO_PERMISSIONS.EXECUTE_PERMISSION_ID ); - const currentExpectedProposalId = 0; + const currentExpectedProposalId = await createProposalId( + plugin.address, + dummyActions, + dummyMetadata + ); await expect( plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) @@ -257,46 +306,6 @@ describe(PLUGIN_CONTRACT_NAME, function () { .withArgs(currentExpectedProposalId); }); - it('correctly increments the proposal ID', async () => { - const { - alice, - initializedPlugin: plugin, - dao, - dummyActions, - dummyMetadata, - } = await loadFixture(fixture); - // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin - await dao.grant( - plugin.address, - alice.address, - EXECUTE_PROPOSAL_PERMISSION_ID - ); - // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO - await dao.grant( - dao.address, - plugin.address, - DAO_PERMISSIONS.EXECUTE_PERMISSION_ID - ); - - const currentExpectedProposalId = 0; - - await plugin - .connect(alice) - .executeProposal(dummyMetadata, dummyActions, 0); - - const nextExpectedProposalId = currentExpectedProposalId + 1; - - const tx = await plugin - .connect(alice) - .executeProposal(dummyMetadata, dummyActions, 0); - - const eventName = plugin.interface.getEvent('ProposalCreated').name; - await expect(tx).to.emit(plugin, eventName); - - const event = findEvent(await tx.wait(), eventName); - expect(event.args.proposalId).to.equal(nextExpectedProposalId); - }); - it("calls the DAO's execute function using the proposal ID as the call ID", async () => { const { alice, @@ -321,7 +330,12 @@ describe(PLUGIN_CONTRACT_NAME, function () { const newPlugin = plugin.connect(alice); { - const proposalId = 0; + const proposalId = await createProposalId( + plugin.address, + dummyActions, + dummyMetadata + ); + const allowFailureMap = 1; const tx = await newPlugin @@ -335,7 +349,7 @@ describe(PLUGIN_CONTRACT_NAME, function () { ); expect(event.args.actor).to.equal(plugin.address); - expect(event.args.callId).to.equal(proposalIdToBytes32(proposalId)); + expect(event.args.callId).to.equal(proposalId); expect(event.args.actions.length).to.equal(1); expect(event.args.actions[0].to).to.equal(dummyActions[0].to); expect(event.args.actions[0].value).to.equal(dummyActions[0].value); @@ -345,20 +359,105 @@ describe(PLUGIN_CONTRACT_NAME, function () { } { - const proposalId = 1; + const newMetadata = dummyMetadata + '11'; + + const proposalId = await createProposalId( + plugin.address, + dummyActions, + newMetadata + ); const tx = await newPlugin .connect(alice) - .executeProposal(dummyMetadata, dummyActions, 0); + .executeProposal(newMetadata, dummyActions, 0); const event = findEventTopicLog( await tx.wait(), dao.interface, dao.interface.getEvent('Executed').name ); - expect(event.args.callId).to.equal(proposalIdToBytes32(proposalId)); + expect(event.args.callId).to.equal(proposalId); } }); + + it('calls executeProposal within createProposal', async () => { + const { + alice, + dummyMetadata, + dummyActions, + dao, + initializedPlugin: plugin, + } = await loadFixture(fixture); + + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + + // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + await expect( + plugin + .connect(alice) + .createProposal(dummyMetadata, dummyActions, 0, 0, '0x') + ).to.emit(plugin, 'ProposalExecuted'); + }); + + it('executes target with delegate call', async () => { + const { + alice, + bob, + dummyMetadata, + dummyActions, + deployer, + dao, + initializedPlugin: plugin, + } = await loadFixture(fixture); + + const executorFactory = new CustomExecutorMock__factory(deployer); + const executor = await executorFactory.deploy(); + + const abiA = CustomExecutorMock__factory.abi; + const abiB = Admin__factory.abi; + // @ts-ignore + const mergedABI = abiA.concat(abiB); + + await dao.grant( + plugin.address, + deployer.address, + SET_TARGET_CONFIG_PERMISSION_ID + ); + + await plugin.connect(deployer).setTargetConfig({ + target: executor.address, + operation: Operation.delegatecall, + }); + + const pluginMerged = (await ethers.getContractAt( + mergedABI, + plugin.address + )) as Admin; + + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + + await expect( + plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 1) + ) + .to.emit(pluginMerged, 'ExecutedCustom') + .to.emit(pluginMerged, 'ProposalExecuted'); + }); }); }); @@ -371,6 +470,7 @@ type FixtureResult = { dao: DAO; dummyActions: DAOStructs.ActionStruct[]; dummyMetadata: string; + targetConfig: TargetConfig; }; async function fixture(): Promise { @@ -383,10 +483,16 @@ async function fixture(): Promise { adminPluginImplementation.address ); + const targetConfig: TargetConfig = { + operation: Operation.call, + target: dao.address, + }; + // Create an initialized plugin clone const adminPluginInitdata = adminPluginImplementation.interface.encodeFunctionData('initialize', [ dao.address, + targetConfig, ]); const deploymentTx1 = await adminProxyFactory.deployMinimalProxy( adminPluginInitdata @@ -427,5 +533,6 @@ async function fixture(): Promise { dao, dummyActions, dummyMetadata, + targetConfig, }; } 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 97df376a..ce817a0a 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -5,7 +5,9 @@ import {AdminSetup, Admin__factory, AdminSetup__factory} from '../../typechain'; import { ADMIN_INTERFACE, EXECUTE_PROPOSAL_PERMISSION_ID, + TargetConfig, } from '../admin-constants'; +import {Operation as Op} from '../admin-constants'; import { Operation, DAO_PERMISSIONS, @@ -26,6 +28,7 @@ type FixtureResult = { prepareInstallationInputs: string; prepareUninstallationInputs: string; dao: DAO; + targetConfig: TargetConfig; }; async function fixture(): Promise { @@ -34,11 +37,16 @@ async function fixture(): Promise { const dao = await createDaoProxy(deployer, dummyMetadata); const pluginSetup = await new AdminSetup__factory(deployer).deploy(); + const targetConfig: TargetConfig = { + operation: Op.call, + target: dao.address, + }; + const prepareInstallationInputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( buildMetadata.pluginSetup.prepareInstallation.inputs ), - [alice.address] + [alice.address, targetConfig] ); const prepareUninstallationInputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( @@ -55,6 +63,7 @@ async function fixture(): Promise { prepareInstallationInputs, prepareUninstallationInputs, dao, + targetConfig, }; } @@ -98,13 +107,13 @@ describe(PLUGIN_SETUP_CONTRACT_NAME, function () { }); it('reverts if encoded address in `_data` is zero', async () => { - const {pluginSetup, dao} = await loadFixture(fixture); + const {pluginSetup, dao, targetConfig} = await loadFixture(fixture); const dataWithAddressZero = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( buildMetadata.pluginSetup.prepareInstallation.inputs ), - [ethers.constants.AddressZero] + [ethers.constants.AddressZero, targetConfig] ); await expect( 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 d98e7ddd..77a62cad 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,6 +1,7 @@ import {METADATA, VERSION} from '../../plugin-settings'; import {AdminSetup, AdminSetup__factory, Admin__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; +import {Operation, TargetConfig} from '../admin-constants'; import {createDaoProxy, installPLugin, uninstallPLugin} from './test-helpers'; import { getLatestNetworkDeployment, @@ -28,9 +29,8 @@ 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( - fixture - ); + const {alice, deployer, psp, dao, pluginSetupRef, targetConfig} = + await loadFixture(fixture); // Grant deployer all required permissions await dao @@ -61,7 +61,7 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs ), - [alice.address] + [alice.address, targetConfig] ) ); @@ -100,6 +100,7 @@ type FixtureResult = { pluginRepo: PluginRepo; pluginSetup: AdminSetup; pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; + targetConfig: TargetConfig; }; async function fixture(): Promise { @@ -138,6 +139,11 @@ async function fixture(): Promise { deployer ); + const targetConfig: TargetConfig = { + operation: Operation.call, + target: dao.address, + }; + const pluginSetupRef = { versionTag: { release: VERSION.release, @@ -155,5 +161,6 @@ async function fixture(): Promise { pluginRepo, pluginSetup, pluginSetupRef, + targetConfig, }; } diff --git a/packages/contracts/test/admin-constants.ts b/packages/contracts/test/admin-constants.ts index 799d0d8e..10625964 100644 --- a/packages/contracts/test/admin-constants.ts +++ b/packages/contracts/test/admin-constants.ts @@ -1,7 +1,6 @@ import {ethers} from 'hardhat'; export const ADMIN_INTERFACE = new ethers.utils.Interface([ - 'function initialize(address)', 'function executeProposal(bytes,tuple(address,uint256,bytes)[],uint256)', ]); @@ -9,3 +8,17 @@ export const ADMIN_INTERFACE = new ethers.utils.Interface([ export const EXECUTE_PROPOSAL_PERMISSION_ID = ethers.utils.id( 'EXECUTE_PROPOSAL_PERMISSION' ); + +export const SET_TARGET_CONFIG_PERMISSION_ID = ethers.utils.id( + 'SET_TARGET_CONFIG_PERMISSION' +); + +export enum Operation { + call, + delegatecall, +} + +export type TargetConfig = { + target: string; + operation: number; +};