From 7dd696207af6db0218d3705d16b575aa53775c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 4 Jun 2024 13:53:33 +0200 Subject: [PATCH 01/14] Unifying proposal wrappers in one place --- .../src/governance/GovernancePluginsSetup.sol | 68 +++++++--- .../src/governance/MainVotingPlugin.sol | 23 ++-- .../src/governance/MemberAccessPlugin.sol | 122 +++++++----------- .../src/test/TestGovernancePluginsSetup.sol | 56 +++++--- .../src/test/TestMemberAccessPlugin.sol | 8 -- 5 files changed, 151 insertions(+), 126 deletions(-) diff --git a/packages/contracts/src/governance/GovernancePluginsSetup.sol b/packages/contracts/src/governance/GovernancePluginsSetup.sol index 48630bb..639c811 100644 --- a/packages/contracts/src/governance/GovernancePluginsSetup.sol +++ b/packages/contracts/src/governance/GovernancePluginsSetup.sol @@ -45,25 +45,34 @@ contract GovernancePluginsSetup is PluginSetup { address _pluginUpgrader ) = decodeInstallationParams(_data); + // Deploy the member access plugin + address _memberAccessPlugin = createERC1967Proxy( + memberAccessPluginImplementation, + abi.encodeCall( + MemberAccessPlugin.initialize, + ( + IDAO(_dao), + MemberAccessPlugin.MultisigSettings({ + proposalDuration: _memberAccessProposalDuration + }) + ) + ) + ); + // Deploy the main voting plugin mainVotingPlugin = createERC1967Proxy( mainVotingPluginImplementation, abi.encodeCall( MainVotingPlugin.initialize, - (IDAO(_dao), _votingSettings, _initialEditors) + ( + IDAO(_dao), + _votingSettings, + _initialEditors, + MemberAccessPlugin(_memberAccessPlugin) + ) ) ); - // Deploy the member access plugin - MemberAccessPlugin.MultisigSettings memory _multisigSettings; - _multisigSettings.proposalDuration = _memberAccessProposalDuration; - _multisigSettings.mainVotingPlugin = MainVotingPlugin(mainVotingPlugin); - - address _memberAccessPlugin = createERC1967Proxy( - memberAccessPluginImplementation, - abi.encodeCall(MemberAccessPlugin.initialize, (IDAO(_dao), _multisigSettings)) - ); - // Condition contract (member access plugin execute) address _memberAccessExecuteCondition = address( new MemberAccessExecuteCondition(mainVotingPlugin) @@ -72,7 +81,7 @@ contract GovernancePluginsSetup is PluginSetup { // List the requested permissions PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[]( - _pluginUpgrader == address(0x0) ? 5 : 6 + _pluginUpgrader == address(0x0) ? 6 : 7 ); // The main voting plugin can execute on the DAO @@ -102,16 +111,26 @@ contract GovernancePluginsSetup is PluginSetup { .UPDATE_ADDRESSES_PERMISSION_ID() }); - // The member access plugin needs to execute on the DAO + // The MainVotingPlugin can create membership proposals on the MemberAccessPlugin permissions[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: _memberAccessPlugin, + who: mainVotingPlugin, + condition: PermissionLib.NO_CONDITION, + permissionId: MemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + }); + + // The member access plugin needs to execute on the DAO + permissions[4] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.GrantWithCondition, where: _dao, who: _memberAccessPlugin, + // Conditional execution condition: _memberAccessExecuteCondition, permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); // The DAO needs to be able to update the member access plugin settings - permissions[4] = PermissionLib.MultiTargetPermission({ + permissions[5] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Grant, where: _memberAccessPlugin, who: _dao, @@ -134,7 +153,7 @@ contract GovernancePluginsSetup is PluginSetup { PluginSetupProcessor(pluginSetupProcessor), _targetPluginAddresses ); - permissions[5] = PermissionLib.MultiTargetPermission({ + permissions[6] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.GrantWithCondition, where: _dao, who: _pluginUpgrader, @@ -162,7 +181,7 @@ contract GovernancePluginsSetup is PluginSetup { address _memberAccessPlugin = _payload.currentHelpers[0]; permissionChanges = new PermissionLib.MultiTargetPermission[]( - _pluginUpgrader == address(0x0) ? 5 : 6 + _pluginUpgrader == address(0x0) ? 6 : 7 ); // Main voting plugin permissions @@ -194,10 +213,19 @@ contract GovernancePluginsSetup is PluginSetup { .UPDATE_ADDRESSES_PERMISSION_ID() }); - // Member access plugin permissions + // Plugin permissions - // The plugin can no longer execute on the DAO + // The MainVotingPlugin can no longer propose on the MemberAccessPlugin permissionChanges[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _memberAccessPlugin, + who: _payload.plugin, + condition: address(0), + permissionId: MemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + }); + + // The plugin can no longer execute on the DAO + permissionChanges[4] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _dao, who: _memberAccessPlugin, @@ -205,7 +233,7 @@ contract GovernancePluginsSetup is PluginSetup { permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); // The DAO can no longer update the plugin settings - permissionChanges[4] = PermissionLib.MultiTargetPermission({ + permissionChanges[5] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _memberAccessPlugin, who: _dao, @@ -217,7 +245,7 @@ contract GovernancePluginsSetup is PluginSetup { if (_pluginUpgrader != address(0x0)) { // pluginUpgrader can no longer make the DAO execute applyUpdate // pluginUpgrader can no longer make the DAO execute grant/revoke - permissionChanges[5] = PermissionLib.MultiTargetPermission({ + permissionChanges[6] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _dao, who: _pluginUpgrader, diff --git a/packages/contracts/src/governance/MainVotingPlugin.sol b/packages/contracts/src/governance/MainVotingPlugin.sol index 3317491..88f5bc9 100644 --- a/packages/contracts/src/governance/MainVotingPlugin.sol +++ b/packages/contracts/src/governance/MainVotingPlugin.sol @@ -10,6 +10,7 @@ import {MajorityVotingBase} from "./base/MajorityVotingBase.sol"; import {IMembers} from "../base/IMembers.sol"; import {IEditors} from "../base/IEditors.sol"; import {Addresslist} from "./base/Addresslist.sol"; +import {MemberAccessPlugin} from "./MemberAccessPlugin.sol"; import {SpacePlugin} from "../space/SpacePlugin.sol"; // The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. @@ -18,6 +19,7 @@ bytes4 constant MAIN_SPACE_VOTING_INTERFACE_ID = MainVotingPlugin.initialize.sel MainVotingPlugin.proposeEdits.selector ^ MainVotingPlugin.proposeAcceptSubspace.selector ^ MainVotingPlugin.proposeRemoveSubspace.selector ^ + MainVotingPlugin.proposeAddMember.selector ^ MainVotingPlugin.proposeRemoveMember.selector ^ MainVotingPlugin.proposeAddEditor.selector ^ MainVotingPlugin.proposeRemoveEditor.selector ^ @@ -45,6 +47,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers /// @notice Whether an address is considered as a space member (not editor) mapping(address => bool) internal members; + /// @notice The address of the plugin where new memberships are approved, using a different set of rules. + MemberAccessPlugin public memberAccessPlugin; + /// @notice Emitted when the creator cancels a proposal event ProposalCanceled(uint256 proposalId); @@ -98,12 +103,15 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers function initialize( IDAO _dao, VotingSettings calldata _votingSettings, - address[] calldata _initialEditors + address[] calldata _initialEditors, + MemberAccessPlugin _memberAccessPlugin ) external initializer { __MajorityVotingBase_init(_dao, _votingSettings); _addAddresses(_initialEditors); emit EditorsAdded(_initialEditors); + + memberAccessPlugin = _memberAccessPlugin; } /// @notice Checks if this or the parent contract supports an interface by its ID. @@ -339,19 +347,18 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers /// @notice Creates a proposal to add a new member. /// @param _metadata The metadata of the proposal. /// @param _proposedMember The address of the member who may eveutnally be added. + /// @return proposalId NOTE: The proposal ID will belong to the Multisig plugin, not to this contract. function proposeAddMember( bytes calldata _metadata, address _proposedMember - ) public onlyMembers returns (uint256 proposalId) { + ) public returns (uint256 proposalId) { if (isMember(_proposedMember)) { revert AlreadyAMember(_proposedMember); } - proposalId = _proposeWrappedAction( - _metadata, - address(this), - abi.encodeCall(MainVotingPlugin.addMember, (_proposedMember)) - ); + /// @dev Creating the actual proposal on a separate plugin because the approval rules differ. + /// @dev Keeping all wrappers on the MainVoting plugin, even if one type of approvals are handled on the MemberAccess plugin. + return memberAccessPlugin.proposeAddMember(_metadata, _proposedMember, msg.sender); } /// @notice Creates a proposal to remove an existing member. @@ -554,5 +561,5 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers /// @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 - uint256[48] private __gap; + uint256[47] private __gap; } diff --git a/packages/contracts/src/governance/MemberAccessPlugin.sol b/packages/contracts/src/governance/MemberAccessPlugin.sol index bfb3527..8d07fbb 100644 --- a/packages/contracts/src/governance/MemberAccessPlugin.sol +++ b/packages/contracts/src/governance/MemberAccessPlugin.sol @@ -8,12 +8,14 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol"; import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgradeable.sol"; +import {Addresslist} from "./base/Addresslist.sol"; import {IMultisig} from "./base/IMultisig.sol"; +import {IEditors} from "../base/IEditors.sol"; import {MainVotingPlugin, MAIN_SPACE_VOTING_INTERFACE_ID} from "./MainVotingPlugin.sol"; bytes4 constant MEMBER_ACCESS_INTERFACE_ID = MemberAccessPlugin.initialize.selector ^ MemberAccessPlugin.updateMultisigSettings.selector ^ - MemberAccessPlugin.proposeNewMember.selector ^ + MemberAccessPlugin.proposeAddMember.selector ^ MemberAccessPlugin.getProposal.selector; /// @title Member access plugin (Multisig) - Release 1, Build 1 @@ -26,6 +28,9 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade bytes32 public constant UPDATE_MULTISIG_SETTINGS_PERMISSION_ID = keccak256("UPDATE_MULTISIG_SETTINGS_PERMISSION"); + /// @notice The ID of the permission required to create new membership proposals. + bytes32 public constant PROPOSER_PERMISSION_ID = keccak256("PROPOSER_PERMISSION"); + /// @notice The minimum total amount of approvals required for proposals created by a non-editor uint16 internal constant MIN_APPROVALS_WHEN_CREATED_BY_NON_EDITOR = uint16(1); @@ -48,6 +53,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade ProposalParameters parameters; mapping(address => bool) approvers; IDAO.Action[] actions; + MainVotingPlugin mainVotingPlugin; uint256 failsafeActionMap; } @@ -68,7 +74,6 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @param mainVotingPlugin The address of the main voting plugin. Used to apply permissions for it. struct MultisigSettings { uint64 proposalDuration; - MainVotingPlugin mainVotingPlugin; } /// @notice A mapping between proposal IDs and proposal information. @@ -81,9 +86,8 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @dev This variable prevents a proposal from being created in the same block in which the multisig settings change. uint64 public lastMultisigSettingsChange; - /// @notice Thrown when a sender is not allowed to create a proposal. - /// @param sender The sender address. - error ProposalCreationForbidden(address sender); + /// @notice Thrown when creating a proposal at the same block that the settings were changed. + error ProposalCreationForbiddenOnSameBlock(); /// @notice Thrown if an approver is not allowed to cast an approve. This can be because the proposal /// - is not open, @@ -97,14 +101,8 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @param proposalId The ID of the proposal. error ProposalExecutionForbidden(uint256 proposalId); - /// @notice Thrown when attempting to use addAddresses. - error AddresslistDisabled(); - - /// @notice Thrown when attempting to use an invalid contract. - error InvalidContract(); - - /// @notice Thrown when attempting request membership for a current member. - error AlreadyMember(address _member); + /// @notice Thrown when called from an incompatible contract. + error InvalidCallerInterface(); /// @notice Emitted when a proposal is approved by an editor. /// @param proposalId The ID of the proposal. @@ -118,8 +116,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @notice Emitted when the plugin settings are set. /// @param proposalDuration The amount of time before a non-approved proposal expires. - /// @param mainVotingPlugin The address of the main voting plugin for the space. Used to apply permissions for it. - event MultisigSettingsUpdated(uint64 proposalDuration, address mainVotingPlugin); + event MultisigSettingsUpdated(uint64 proposalDuration); /// @notice Initializes Release 1, Build 1. /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). @@ -154,14 +151,35 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade _updateMultisigSettings(_multisigSettings); } - /// @notice Creates a new multisig proposal wrapped by proposeNewMember. + /// @notice Creates a proposal to add a new member. /// @param _metadata The metadata of the proposal. - /// @param _actions A list of actions wrapped by proposeNewMember. + /// @param _proposedMember The address of the member who may eveutnally be added. + /// @param _proposer The address to use as the proposal creator. /// @return proposalId The ID of the proposal. - function createProposal( + function proposeAddMember( bytes calldata _metadata, - IDAO.Action[] memory _actions - ) internal returns (uint256 proposalId) { + address _proposedMember, + address _proposer + ) external auth(PROPOSER_PERMISSION_ID) returns (uint256 proposalId) { + // Check that the caller supports the `addMember` function + if ( + !MainVotingPlugin(msg.sender).supportsInterface(MAIN_SPACE_VOTING_INTERFACE_ID) || + !MainVotingPlugin(msg.sender).supportsInterface(type(IEditors).interfaceId) || + !MainVotingPlugin(msg.sender).supportsInterface(type(Addresslist).interfaceId) + ) { + revert InvalidCallerInterface(); + } + + // Build the list of actions + IDAO.Action[] memory _actions = new IDAO.Action[](1); + + _actions[0] = IDAO.Action({ + to: address(msg.sender), // We are called by the MainVotingPlugin + value: 0, + data: abi.encodeCall(MainVotingPlugin.addMember, (_proposedMember)) + }); + + // Create proposal uint64 snapshotBlock; unchecked { snapshotBlock = block.number.toUint64() - 1; // The snapshot block must be mined already to protect the transaction against backrunning transactions causing census changes. @@ -170,7 +188,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade // Revert if the settings have been changed in the same block as this proposal should be created in. // This prevents a malicious party from voting with previous addresses and the new settings. if (lastMultisigSettingsChange > snapshotBlock) { - revert ProposalCreationForbidden(msg.sender); + revert ProposalCreationForbiddenOnSameBlock(); } uint64 _startDate = block.timestamp.toUint64(); @@ -180,7 +198,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade emit ProposalCreated({ proposalId: proposalId, - creator: msg.sender, + creator: _proposer, metadata: _metadata, startDate: _startDate, endDate: _endDate, @@ -195,6 +213,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade proposal_.parameters.startDate = _startDate; proposal_.parameters.endDate = _endDate; + proposal_.mainVotingPlugin = MainVotingPlugin(msg.sender); for (uint256 i; i < _actions.length; ) { proposal_.actions.push(_actions[i]); unchecked { @@ -202,8 +221,12 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade } } - if (isEditor(msg.sender)) { - if (multisigSettings.mainVotingPlugin.addresslistLength() < 2) { + // Another editor needs to approve. Set the minApprovals accordingly + /// @dev The _proposer parameter is technically trusted. + /// @dev However, this function is protected by PROPOSER_PERMISSION_ID and only the MainVoting plugin is granted this permission. + /// @dev See GovernancePluginsSetup.sol + if (MainVotingPlugin(msg.sender).isEditor(_proposer)) { + if (MainVotingPlugin(msg.sender).addresslistLength() < 2) { proposal_.parameters.minApprovals = MIN_APPROVALS_WHEN_CREATED_BY_SINGLE_EDITOR; } else { proposal_.parameters.minApprovals = MIN_APPROVALS_WHEN_CREATED_BY_EDITOR_OF_MANY; @@ -216,30 +239,6 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade } } - /// @notice Creates a proposal to add a new member. - /// @param _metadata The metadata of the proposal. - /// @param _proposedMember The address of the member who may eveutnally be added. - /// @return proposalId The ID of the proposal. - function proposeNewMember( - bytes calldata _metadata, - address _proposedMember - ) external returns (uint256 proposalId) { - if (isMember(_proposedMember)) { - revert AlreadyMember(_proposedMember); - } - - // Build the list of actions - IDAO.Action[] memory _actions = new IDAO.Action[](1); - - _actions[0] = IDAO.Action({ - to: address(multisigSettings.mainVotingPlugin), - value: 0, - data: abi.encodeCall(MainVotingPlugin.addMember, (_proposedMember)) - }); - - return createProposal(_metadata, _actions); - } - /// @inheritdoc IMultisig /// @dev The second parameter is left empty to keep compatibility with the existing multisig interface function approve(uint256 _proposalId) public { @@ -331,18 +330,6 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade _execute(_proposalId); } - /// @notice Returns whether the given address holds membership permission on the main voting plugin - function isMember(address _account) public view returns (bool) { - // Does the address hold the member or editor permission on the main voting plugin? - return multisigSettings.mainVotingPlugin.isMember(_account); - } - - /// @notice Returns whether the given address holds editor permission on the main voting plugin - function isEditor(address _account) public view returns (bool) { - // Does the address hold the permission on the main voting plugin? - return multisigSettings.mainVotingPlugin.isEditor(_account); - } - /// @notice Internal function to execute a vote. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. function _execute(uint256 _proposalId) internal { @@ -368,7 +355,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade if (!_isProposalOpen(proposal_)) { // The proposal was executed already return false; - } else if (!isEditor(_account)) { + } else if (!proposal_.mainVotingPlugin.isEditor(_account)) { // The approver has no voting power. return false; } else if (proposal_.approvers[_account]) { @@ -407,21 +394,10 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @notice Internal function to update the plugin settings. /// @param _multisigSettings The new settings. function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { - if ( - !MainVotingPlugin(_multisigSettings.mainVotingPlugin).supportsInterface( - MAIN_SPACE_VOTING_INTERFACE_ID - ) - ) { - revert InvalidContract(); - } - multisigSettings = _multisigSettings; lastMultisigSettingsChange = block.number.toUint64(); - emit MultisigSettingsUpdated({ - proposalDuration: _multisigSettings.proposalDuration, - mainVotingPlugin: address(_multisigSettings.mainVotingPlugin) - }); + emit MultisigSettingsUpdated({proposalDuration: _multisigSettings.proposalDuration}); } /// @dev This empty reserved space is put in place to allow future versions to add new diff --git a/packages/contracts/src/test/TestGovernancePluginsSetup.sol b/packages/contracts/src/test/TestGovernancePluginsSetup.sol index 1ae3e18..2b247c5 100644 --- a/packages/contracts/src/test/TestGovernancePluginsSetup.sol +++ b/packages/contracts/src/test/TestGovernancePluginsSetup.sol @@ -44,25 +44,29 @@ contract TestGovernancePluginsSetup is PluginSetup { address _pluginUpgrader ) = decodeInstallationParams(_data); - // Deploy the main voting plugin - mainVotingPlugin = createERC1967Proxy( - mainVotingPluginImplementationAddr, - abi.encodeCall( - MainVotingPlugin.initialize, - (IDAO(_dao), _votingSettings, _initialEditors) - ) - ); - // Deploy the member access plugin TestMemberAccessPlugin.MultisigSettings memory _multisigSettings; _multisigSettings.proposalDuration = _memberAccessProposalDuration; - _multisigSettings.mainVotingPlugin = MainVotingPlugin(mainVotingPlugin); address _memberAccessPlugin = createERC1967Proxy( memberAccessPluginImplementation(), abi.encodeCall(TestMemberAccessPlugin.initialize, (IDAO(_dao), _multisigSettings)) ); + // Deploy the main voting plugin + mainVotingPlugin = createERC1967Proxy( + mainVotingPluginImplementationAddr, + abi.encodeCall( + MainVotingPlugin.initialize, + ( + IDAO(_dao), + _votingSettings, + _initialEditors, + TestMemberAccessPlugin(_memberAccessPlugin) + ) + ) + ); + // Condition contract (member access plugin execute) address _memberAccessExecuteCondition = address( new MemberAccessExecuteCondition(mainVotingPlugin) @@ -71,7 +75,7 @@ contract TestGovernancePluginsSetup is PluginSetup { // List the requested permissions PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[]( - _pluginUpgrader == address(0x0) ? 5 : 6 + _pluginUpgrader == address(0x0) ? 6 : 7 ); // The main voting plugin can execute on the DAO @@ -103,6 +107,15 @@ contract TestGovernancePluginsSetup is PluginSetup { // The member access plugin needs to execute on the DAO permissions[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.GrantWithCondition, + where: _memberAccessPlugin, + who: mainVotingPlugin, + condition: PermissionLib.NO_CONDITION, + permissionId: TestMemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + }); + + // The member access plugin needs to execute on the DAO + permissions[4] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.GrantWithCondition, where: _dao, who: _memberAccessPlugin, @@ -110,7 +123,7 @@ contract TestGovernancePluginsSetup is PluginSetup { permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); // The DAO needs to be able to update the member access plugin settings - permissions[4] = PermissionLib.MultiTargetPermission({ + permissions[5] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Grant, where: _memberAccessPlugin, who: _dao, @@ -133,7 +146,7 @@ contract TestGovernancePluginsSetup is PluginSetup { PluginSetupProcessor(pluginSetupProcessor), _targetPluginAddresses ); - permissions[5] = PermissionLib.MultiTargetPermission({ + permissions[6] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.GrantWithCondition, where: _dao, who: _pluginUpgrader, @@ -193,7 +206,7 @@ contract TestGovernancePluginsSetup is PluginSetup { address _memberAccessPlugin = _payload.currentHelpers[0]; permissionChanges = new PermissionLib.MultiTargetPermission[]( - _pluginUpgrader == address(0x0) ? 5 : 6 + _pluginUpgrader == address(0x0) ? 6 : 7 ); // Main voting plugin permissions @@ -227,8 +240,17 @@ contract TestGovernancePluginsSetup is PluginSetup { // Member access plugin permissions - // The plugin can no longer execute on the DAO + // The member access plugin needs to execute on the DAO permissionChanges[3] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _memberAccessPlugin, + who: _payload.plugin, + condition: PermissionLib.NO_CONDITION, + permissionId: TestMemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + }); + + // The plugin can no longer execute on the DAO + permissionChanges[4] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _dao, who: _memberAccessPlugin, @@ -236,7 +258,7 @@ contract TestGovernancePluginsSetup is PluginSetup { permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); // The DAO can no longer update the plugin settings - permissionChanges[4] = PermissionLib.MultiTargetPermission({ + permissionChanges[5] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _memberAccessPlugin, who: _dao, @@ -248,7 +270,7 @@ contract TestGovernancePluginsSetup is PluginSetup { if (_pluginUpgrader != address(0x0)) { // pluginUpgrader can no longer make the DAO execute applyUpdate // pluginUpgrader can no longer make the DAO execute grant/revoke - permissionChanges[5] = PermissionLib.MultiTargetPermission({ + permissionChanges[6] = PermissionLib.MultiTargetPermission({ operation: PermissionLib.Operation.Revoke, where: _dao, who: _pluginUpgrader, diff --git a/packages/contracts/src/test/TestMemberAccessPlugin.sol b/packages/contracts/src/test/TestMemberAccessPlugin.sol index 5bcdf0d..ade26f9 100644 --- a/packages/contracts/src/test/TestMemberAccessPlugin.sol +++ b/packages/contracts/src/test/TestMemberAccessPlugin.sol @@ -7,14 +7,6 @@ import {MemberAccessPlugin} from "../governance/MemberAccessPlugin.sol"; /// @notice A clone of the MemberAccessPlugin contract, just to test contract TestMemberAccessPlugin is MemberAccessPlugin { - function createArbitraryProposal( - bytes calldata _metadata, - IDAO.Action[] memory _actions - ) public returns (uint256 proposalId) { - // Exposing createProposal for E2E testing - return createProposal(_metadata, _actions); - } - function initialize( IDAO _dao, MultisigSettings calldata _multisigSettings From d6f184ef13aa215ba82becb45757b0724040e391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 4 Jun 2024 16:45:30 +0200 Subject: [PATCH 02/14] Work in progress --- .../test/unit-testing/main-voting-plugin.ts | 102 +++++++++++------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/packages/contracts/test/unit-testing/main-voting-plugin.ts b/packages/contracts/test/unit-testing/main-voting-plugin.ts index 69b9613..76e0601 100644 --- a/packages/contracts/test/unit-testing/main-voting-plugin.ts +++ b/packages/contracts/test/unit-testing/main-voting-plugin.ts @@ -88,11 +88,13 @@ describe('Main Voting Plugin', function () { // inits await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); - await mainVotingPlugin.initialize(dao.address, defaultMainVotingSettings, [ - alice.address, - ]); + await mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + memberAccessPlugin.address + ); await spacePlugin.initialize( dao.address, defaultInput.contentUri, @@ -141,7 +143,7 @@ describe('Main Voting Plugin', function () { // Alice is already an editor (see initialize) // Bob is a member - await memberAccessPlugin.proposeNewMember('0x', bob.address); + await mainVotingPlugin.proposeAddMember('0x', bob.address); }); describe('initialize', async () => { @@ -149,13 +151,15 @@ describe('Main Voting Plugin', function () { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.revertedWith('Initializable: contract is already initialized'); await expect( - mainVotingPlugin.initialize(dao.address, defaultMainVotingSettings, [ - alice.address, - ]) + mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + memberAccessPlugin.address + ) ).to.be.revertedWith('Initializable: contract is already initialized'); await expect( spacePlugin.initialize( @@ -174,7 +178,8 @@ describe('Main Voting Plugin', function () { await mainVotingPlugin.initialize( dao.address, defaultMainVotingSettings, - [alice.address] + [alice.address], + memberAccessPlugin.address ); await mineBlock(); @@ -196,7 +201,8 @@ describe('Main Voting Plugin', function () { await mainVotingPlugin.initialize( dao.address, defaultMainVotingSettings, - [bob.address] + [bob.address], + memberAccessPlugin.address ); await mineBlock(); @@ -421,7 +427,7 @@ describe('Main Voting Plugin', function () { expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); - await memberAccessPlugin.proposeNewMember('0x', carol.address); + await mainVotingPlugin.proposeAddMember('0x', carol.address); expect(await mainVotingPlugin.isMember(carol.address)).to.eq(true); await mainVotingPlugin.proposeRemoveMember('0x', carol.address); @@ -487,7 +493,7 @@ describe('Main Voting Plugin', function () { it('Proposals created by a member require editor votes', async () => { let pid = 0; // Carol member - await memberAccessPlugin.proposeNewMember('0x', carol.address); + await mainVotingPlugin.proposeAddMember('0x', carol.address); // Bob editor await proposeNewEditor(bob.address); @@ -1390,12 +1396,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.votingMode = VotingMode.Standard; - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -1548,12 +1556,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.votingMode = VotingMode.EarlyExecution; - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -1772,12 +1782,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.votingMode = VotingMode.VoteReplacement; - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -1938,12 +1950,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () describe('Different configurations:', async () => { describe('A simple majority vote with >50% support and early execution', async () => { beforeEach(async () => { - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -2064,12 +2078,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.supportThreshold = pctToRatio(0); - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -2132,12 +2148,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.supportThreshold = pctToRatio(100).sub(1); - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); await makeEditors(signers.slice(1)); // editors 2-10 @@ -2205,12 +2223,14 @@ describe.skip('Tests replicated from the original AddressList plugin', async () beforeEach(async () => { votingSettings.supportThreshold = pctToRatio(50); - await mainVotingPlugin.initialize(dao.address, votingSettings, [ - signers[0].address, - ]); + await mainVotingPlugin.initialize( + dao.address, + votingSettings, + [signers[0].address], + memberAccessPlugin.address + ); await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); await makeMembers(signers); // 10 members await makeEditors(signers.slice(0, 5)); // editors 0-5 From 6459412aaf1696b040c4810421e2de4178ce39d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:02:45 +0200 Subject: [PATCH 03/14] Minor edit --- .../contracts/test/unit-testing/main-voting-plugin.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/contracts/test/unit-testing/main-voting-plugin.ts b/packages/contracts/test/unit-testing/main-voting-plugin.ts index 76e0601..6a83736 100644 --- a/packages/contracts/test/unit-testing/main-voting-plugin.ts +++ b/packages/contracts/test/unit-testing/main-voting-plugin.ts @@ -453,7 +453,7 @@ describe('Main Voting Plugin', function () { }); }); - context.skip('One editor', () => { + context('One editor', () => { it('Proposals take immediate effect when created by the only editor', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); @@ -489,7 +489,7 @@ describe('Main Voting Plugin', function () { }); }); - context.skip('Multiple editors', () => { + context('Multiple editors', () => { it('Proposals created by a member require editor votes', async () => { let pid = 0; // Carol member @@ -558,7 +558,7 @@ describe('Main Voting Plugin', function () { }); }); - context.skip('Canceling', () => { + context('Canceling', () => { it('Proposals created by a member can be canceled before they end', async () => { const proposalId = 0; await expect( @@ -940,7 +940,7 @@ describe('Main Voting Plugin', function () { }); }); - context.skip('After proposals', () => { + context('After proposals', () => { it('Adding an editor increases the editorCount', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); @@ -1316,7 +1316,7 @@ describe('Main Voting Plugin', function () { // TESTS REPLIACTED FROM THE ORIGINAL ADDRESS LIST PLUGIN -describe.skip('Tests replicated from the original AddressList plugin', async () => { +describe('Tests replicated from the original AddressList plugin', async () => { let signers: SignerWithAddress[]; let dao: DAO; let mainVotingPlugin: MainVotingPlugin; From 87873e5232fa2f1833e1c19f995e03c8a26c9767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 4 Jun 2024 18:34:23 +0200 Subject: [PATCH 04/14] Testing, work in progress --- .../src/governance/MemberAccessPlugin.sol | 2 +- .../src/test/TestMemberAccessPlugin.sol | 8 + .../member-access-condition.ts | 159 +------- .../contracts/test/unit-testing/common.ts | 1 + .../unit-testing/governance-plugins-setup.ts | 45 ++- .../test/unit-testing/main-voting-plugin.ts | 7 + .../test/unit-testing/member-access-plugin.ts | 344 +++++++++--------- 7 files changed, 223 insertions(+), 343 deletions(-) diff --git a/packages/contracts/src/governance/MemberAccessPlugin.sol b/packages/contracts/src/governance/MemberAccessPlugin.sol index 8d07fbb..fc4d871 100644 --- a/packages/contracts/src/governance/MemberAccessPlugin.sol +++ b/packages/contracts/src/governance/MemberAccessPlugin.sol @@ -160,7 +160,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade bytes calldata _metadata, address _proposedMember, address _proposer - ) external auth(PROPOSER_PERMISSION_ID) returns (uint256 proposalId) { + ) public auth(PROPOSER_PERMISSION_ID) returns (uint256 proposalId) { // Check that the caller supports the `addMember` function if ( !MainVotingPlugin(msg.sender).supportsInterface(MAIN_SPACE_VOTING_INTERFACE_ID) || diff --git a/packages/contracts/src/test/TestMemberAccessPlugin.sol b/packages/contracts/src/test/TestMemberAccessPlugin.sol index ade26f9..8b7c442 100644 --- a/packages/contracts/src/test/TestMemberAccessPlugin.sol +++ b/packages/contracts/src/test/TestMemberAccessPlugin.sol @@ -15,4 +15,12 @@ contract TestMemberAccessPlugin is MemberAccessPlugin { _updateMultisigSettings(_multisigSettings); } + + function devProposeAddMember( + bytes calldata _metadata, + address _proposedMember, + address _proposer + ) public returns (uint256 proposalId) { + return proposeAddMember(_metadata, _proposedMember, _proposer); + } } diff --git a/packages/contracts/test/integration-testing/member-access-condition.ts b/packages/contracts/test/integration-testing/member-access-condition.ts index b45092b..2e1f0e2 100644 --- a/packages/contracts/test/integration-testing/member-access-condition.ts +++ b/packages/contracts/test/integration-testing/member-access-condition.ts @@ -162,159 +162,14 @@ describe('Member Access Condition E2E', () => { it('Executing a proposal to add membership works', async () => { expect(await mainVotingPlugin.isMember(alice.address)).to.eq(false); - await expect(memberAccessPlugin.proposeNewMember('0x', alice.address)).to - .not.be.reverted; + await expect( + memberAccessPlugin.devProposeAddMember( + '0x', + alice.address, + deployer.address + ) + ).to.not.be.reverted; expect(await mainVotingPlugin.isMember(alice.address)).to.eq(true); - - // Valid addition - const actions: IDAO.ActionStruct[] = [ - { - to: mainVotingPlugin.address, - value: 0, - data: mainVotingInterface.encodeFunctionData('addMember', [ - bob.address, - ]), - }, - ]; - - // Via direct create proposal - expect(await mainVotingPlugin.isMember(bob.address)).to.eq(false); - - await expect(memberAccessPlugin.createArbitraryProposal('0x', actions)).to - .not.be.reverted; - - expect(await mainVotingPlugin.isMember(bob.address)).to.eq(true); - }); - - it('Executing a proposal to do something else reverts', async () => { - const validActions = [ - { - to: mainVotingPlugin.address, - value: 0, - data: mainVotingInterface.encodeFunctionData('addMember', [ - bob.address, - ]), - }, - { - to: mainVotingPlugin.address, - value: 0, - data: mainVotingInterface.encodeFunctionData('addMember', [ - ADDRESS_ONE, - ]), - }, - ]; - const invalidActions = [ - { - to: mainVotingPlugin.address, - value: 0, - data: mainVotingInterface.encodeFunctionData('removeMember', [ - bob.address, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - mainVotingPlugin.address, - bob.address, - EXECUTE_PERMISSION_ID, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - dao.address, - alice.address, - EXECUTE_PERMISSION_ID, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - dao.address, - alice.address, - ROOT_PERMISSION_ID, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - psp.address, - alice.address, - ethers.utils.id('APPLY_INSTALLATION_PERMISSION'), - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - psp.address, - alice.address, - ethers.utils.id('APPLY_UPDATE_PERMISSION'), - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - psp.address, - alice.address, - ethers.utils.id('APPLY_UNINSTALLATION_PERMISSION'), - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - mainVotingPlugin.address, - alice.address, - UPGRADE_PLUGIN_PERMISSION_ID, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('revoke', [ - mainVotingPlugin.address, - bob.address, - ROOT_PERMISSION_ID, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('execute', [ - ONE_BYTES32, - validActions, - 0, - ]), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('setMetadata', ['0x']), - }, - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('setDaoURI', ['0x']), - }, - ]; - - // Should work - for (const action of validActions) { - await expect(memberAccessPlugin.createArbitraryProposal('0x', [action])) - .to.not.be.reverted; - } - - // Should fail - for (const action of invalidActions) { - await expect(memberAccessPlugin.createArbitraryProposal('0x', [action])) - .to.be.reverted; - } }); }); diff --git a/packages/contracts/test/unit-testing/common.ts b/packages/contracts/test/unit-testing/common.ts index 80325db..e26725d 100644 --- a/packages/contracts/test/unit-testing/common.ts +++ b/packages/contracts/test/unit-testing/common.ts @@ -28,6 +28,7 @@ export const UPDATE_ADDRESSES_PERMISSION_ID = ethers.utils.id( export const UPGRADE_PLUGIN_PERMISSION_ID = ethers.utils.id( 'UPGRADE_PLUGIN_PERMISSION' ); +export const PROPOSER_PERMISSION_ID = ethers.utils.id('PROPOSER_PERMISSION'); export const ROOT_PERMISSION_ID = ethers.utils.id('ROOT_PERMISSION'); export const MAX_UINT64 = ethers.BigNumber.from(2).pow(64).sub(1); diff --git a/packages/contracts/test/unit-testing/governance-plugins-setup.ts b/packages/contracts/test/unit-testing/governance-plugins-setup.ts index dee59e1..661cd7e 100644 --- a/packages/contracts/test/unit-testing/governance-plugins-setup.ts +++ b/packages/contracts/test/unit-testing/governance-plugins-setup.ts @@ -11,6 +11,7 @@ import { EXECUTE_PERMISSION_ID, NO_CONDITION, pctToRatio, + PROPOSER_PERMISSION_ID, UPDATE_ADDRESSES_PERMISSION_ID, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, UPDATE_VOTING_SETTINGS_PERMISSION_ID, @@ -58,12 +59,12 @@ describe('Governance Plugins Setup', function () { const nonce = await ethers.provider.getTransactionCount( governancePluginsSetup.address ); - const anticipatedMainVotingPluginAddress = + const anticipatedMemberAccessPluginAddress = ethers.utils.getContractAddress({ from: governancePluginsSetup.address, nonce, }); - const anticipatedMemberAccessPluginAddress = + const anticipatedMainVotingPluginAddress = ethers.utils.getContractAddress({ from: governancePluginsSetup.address, nonce: nonce + 1, @@ -86,7 +87,7 @@ describe('Governance Plugins Setup', function () { const [memberAccessPlugin] = helpers; expect(memberAccessPlugin).to.eq(anticipatedMemberAccessPluginAddress); - expect(permissions.length).to.be.equal(5); + expect(permissions.length).to.be.equal(6); expect(permissions).to.deep.equal([ [ Operation.Grant, @@ -109,6 +110,13 @@ describe('Governance Plugins Setup', function () { NO_CONDITION, UPDATE_ADDRESSES_PERMISSION_ID, ], + [ + Operation.Grant, + memberAccessPlugin, + mainVotingPlugin, + NO_CONDITION, + PROPOSER_PERMISSION_ID, + ], [ Operation.GrantWithCondition, dao.address, @@ -152,12 +160,12 @@ describe('Governance Plugins Setup', function () { const nonce = await ethers.provider.getTransactionCount( governancePluginsSetup.address ); - const anticipatedMainVotingPluginAddress = + const anticipatedMemberAccessPluginAddress = ethers.utils.getContractAddress({ from: governancePluginsSetup.address, nonce, }); - const anticipatedMemberAccessPluginAddress = + const anticipatedMainVotingPluginAddress = ethers.utils.getContractAddress({ from: governancePluginsSetup.address, nonce: nonce + 1, @@ -185,7 +193,7 @@ describe('Governance Plugins Setup', function () { const [memberAccessPlugin] = helpers; expect(memberAccessPlugin).to.eq(anticipatedMemberAccessPluginAddress); - expect(permissions.length).to.be.equal(6); + expect(permissions.length).to.be.equal(7); expect(permissions).to.deep.equal([ [ Operation.Grant, @@ -208,6 +216,13 @@ describe('Governance Plugins Setup', function () { NO_CONDITION, UPDATE_ADDRESSES_PERMISSION_ID, ], + [ + Operation.Grant, + memberAccessPlugin, + mainVotingPlugin, + NO_CONDITION, + PROPOSER_PERMISSION_ID, + ], [ Operation.GrantWithCondition, dao.address, @@ -264,7 +279,7 @@ describe('Governance Plugins Setup', function () { } ); - expect(permissions.length).to.be.equal(5); + expect(permissions.length).to.be.equal(6); expect(permissions).to.deep.equal([ [ Operation.Revoke, @@ -287,6 +302,13 @@ describe('Governance Plugins Setup', function () { NO_CONDITION, UPDATE_ADDRESSES_PERMISSION_ID, ], + [ + Operation.Revoke, + memberAccessPlugin, + mainVotingPlugin, + NO_CONDITION, + PROPOSER_PERMISSION_ID, + ], [ Operation.Revoke, dao.address, @@ -325,7 +347,7 @@ describe('Governance Plugins Setup', function () { } ); - expect(permissions.length).to.be.equal(6); + expect(permissions.length).to.be.equal(7); expect(permissions).to.deep.equal([ [ Operation.Revoke, @@ -348,6 +370,13 @@ describe('Governance Plugins Setup', function () { NO_CONDITION, UPDATE_ADDRESSES_PERMISSION_ID, ], + [ + Operation.Revoke, + memberAccessPlugin, + mainVotingPlugin, + NO_CONDITION, + PROPOSER_PERMISSION_ID, + ], [ Operation.Revoke, dao.address, diff --git a/packages/contracts/test/unit-testing/main-voting-plugin.ts b/packages/contracts/test/unit-testing/main-voting-plugin.ts index 6a83736..34e8598 100644 --- a/packages/contracts/test/unit-testing/main-voting-plugin.ts +++ b/packages/contracts/test/unit-testing/main-voting-plugin.ts @@ -43,6 +43,7 @@ import { VotingSettings, ZERO_BYTES32, SUBSPACE_PERMISSION_ID, + PROPOSER_PERMISSION_ID, } from './common'; import {defaultMainVotingSettings} from './common'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -137,6 +138,12 @@ describe('Main Voting Plugin', function () { await dao.grant(spacePlugin.address, dao.address, SUBSPACE_PERMISSION_ID); // The DAO is ROOT on itself await dao.grant(dao.address, dao.address, ROOT_PERMISSION_ID); + // The plugin can propose members on the member access helper + await dao.grant( + memberAccessPlugin.address, + mainVotingPlugin.address, + PROPOSER_PERMISSION_ID + ); // Alice can make the DAO execute arbitrary stuff (test) await dao.grant(dao.address, alice.address, EXECUTE_PERMISSION_ID); diff --git a/packages/contracts/test/unit-testing/member-access-plugin.ts b/packages/contracts/test/unit-testing/member-access-plugin.ts index ba01d81..e766723 100644 --- a/packages/contracts/test/unit-testing/member-access-plugin.ts +++ b/packages/contracts/test/unit-testing/member-access-plugin.ts @@ -96,11 +96,13 @@ describe('Member Access Plugin', function () { // inits await memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }); - await mainVotingPlugin.initialize(dao.address, defaultMainVotingSettings, [ - alice.address, - ]); + await mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + memberAccessPlugin.address + ); await spacePlugin.initialize( dao.address, defaultInput.contentUri, @@ -147,7 +149,7 @@ describe('Member Access Plugin', function () { // Bob is a member await mineBlock(); - await memberAccessPlugin.proposeNewMember('0x', bob.address); + await mainVotingPlugin.proposeAddMember('0x', bob.address); }); describe('initialize', () => { @@ -159,7 +161,6 @@ describe('Member Access Plugin', function () { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.not.be.reverted; @@ -170,7 +171,6 @@ describe('Member Access Plugin', function () { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: ADDRESS_ONE, }) ).to.be.reverted; @@ -181,7 +181,6 @@ describe('Member Access Plugin', function () { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: carol.address, }) ).to.be.reverted; }); @@ -190,12 +189,12 @@ describe('Member Access Plugin', function () { describe('Before approving', () => { it('Allows any address to request membership', async () => { // Random - expect(await memberAccessPlugin.isMember(carol.address)).to.be.false; + expect(await mainVotingPlugin.isMember(carol.address)).to.be.false; pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; let proposal = await memberAccessPlugin.getProposal(pid); @@ -204,15 +203,15 @@ describe('Member Access Plugin', function () { expect(proposal.parameters.minApprovals).to.eq(1); expect(proposal.actions.length).to.eq(1); expect(proposal.failsafeActionMap).to.eq(0); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); // Member pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(bob) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) ).to.not.be.reverted; proposal = await memberAccessPlugin.getProposal(pid); @@ -221,14 +220,14 @@ describe('Member Access Plugin', function () { expect(proposal.parameters.minApprovals).to.eq(1); expect(proposal.actions.length).to.eq(1); expect(proposal.failsafeActionMap).to.eq(0); - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); // Editor await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) ).to.not.be.reverted; proposal = await memberAccessPlugin.getProposal(1); @@ -238,21 +237,21 @@ describe('Member Access Plugin', function () { expect(proposal.actions.length).to.eq(1); expect(proposal.failsafeActionMap).to.eq(0); // Auto executed - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(true); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(true); expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(true); }); it('Editors should be members too', async () => { - expect(await memberAccessPlugin.isMember(alice.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(alice.address)).to.eq(true); expect(await mainVotingPlugin.isMember(alice.address)).to.eq(true); }); it('Emits an event when membership is requested', async () => { pid = await memberAccessPlugin.proposalCount(); - const tx = await memberAccessPlugin + const tx = await mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://2345'), carol.address); + .proposeAddMember(toUtf8Bytes('ipfs://2345'), carol.address); await expect(tx).to.emit(memberAccessPlugin, 'ProposalCreated'); @@ -281,39 +280,39 @@ describe('Member Access Plugin', function () { it('isMember() returns true when appropriate', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); - expect(await memberAccessPlugin.isMember(ADDRESS_ZERO)).to.eq(false); - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ZERO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(false); - expect(await memberAccessPlugin.isMember(alice.address)).to.eq(true); - expect(await memberAccessPlugin.isMember(bob.address)).to.eq(true); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(alice.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(bob.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); - await memberAccessPlugin.proposeNewMember('0x', carol.address); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(true); + await mainVotingPlugin.proposeAddMember('0x', carol.address); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(true); await mainVotingPlugin.proposeRemoveMember('0x', carol.address); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); await proposeNewEditor(carol.address); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(true); }); it('isEditor() returns true when appropriate', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); - expect(await memberAccessPlugin.isEditor(ADDRESS_ZERO)).to.eq(false); - expect(await memberAccessPlugin.isEditor(ADDRESS_ONE)).to.eq(false); - expect(await memberAccessPlugin.isEditor(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isEditor(ADDRESS_ZERO)).to.eq(false); + expect(await mainVotingPlugin.isEditor(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isEditor(ADDRESS_TWO)).to.eq(false); - expect(await memberAccessPlugin.isEditor(alice.address)).to.eq(true); - expect(await memberAccessPlugin.isEditor(bob.address)).to.eq(false); - expect(await memberAccessPlugin.isEditor(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isEditor(alice.address)).to.eq(true); + expect(await mainVotingPlugin.isEditor(bob.address)).to.eq(false); + expect(await mainVotingPlugin.isEditor(carol.address)).to.eq(false); await proposeNewEditor(carol.address); - expect(await memberAccessPlugin.isEditor(carol.address)).to.eq(true); + expect(await mainVotingPlugin.isEditor(carol.address)).to.eq(true); }); }); @@ -323,25 +322,25 @@ describe('Member Access Plugin', function () { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); // Approve it (Bob) => fail await expect(memberAccessPlugin.connect(bob).approve(pid)).to.be.reverted; // Still not a member - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); // Approve it (Alice) => success await expect(memberAccessPlugin.connect(alice).approve(pid)).to.not.be .reverted; // Now Carol is a member - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(true); }); it('Only the editor can reject memberships', async () => { @@ -351,9 +350,9 @@ describe('Member Access Plugin', function () { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); @@ -381,9 +380,9 @@ describe('Member Access Plugin', function () { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; // Approve it (Alice) => success @@ -403,9 +402,9 @@ describe('Member Access Plugin', function () { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; // Reject it (Alice) => success @@ -422,48 +421,48 @@ describe('Member Access Plugin', function () { it('Proposal execution is immediate when created by the only editor', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(false); // Alice proposes await expect( - memberAccessPlugin.proposeNewMember( + mainVotingPlugin.proposeAddMember( toUtf8Bytes('ipfs://1234'), carol.address ) ).to.not.be.reverted; // Now Carol is a member - expect(await memberAccessPlugin.isMember(carol.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(carol.address)).to.eq(true); }); it("Proposals created by a non-editor need an editor's approval", async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(1); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; const proposal = await memberAccessPlugin.getProposal(pid); expect(proposal.executed).to.eq(false); expect(proposal.parameters.minApprovals).to.eq(1); expect(await memberAccessPlugin.canExecute(pid)).to.eq(false); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Dave cannot await expect(memberAccessPlugin.connect(dave).approve(pid)).to.be .reverted; await expect(memberAccessPlugin.connect(dave).execute(pid)).to.be .reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Alice can await expect(memberAccessPlugin.connect(alice).approve(pid)).to.not.be .reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(true); }); }); @@ -486,76 +485,76 @@ describe('Member Access Plugin', function () { expect(await mainVotingPlugin.addresslistLength()).to.eq(3); // Requesting membership for Dave - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Dave cannot approve (fail) await expect(memberAccessPlugin.connect(dave).approve(pid)).to.be .reverted; // Dave is still not a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Approve it (Alice) await expect(memberAccessPlugin.connect(alice).approve(pid)).to.not.be .reverted; // Dave is now a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(true); // Now requesting for 0x1 - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) ).to.not.be.reverted; - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); // Dave cannot approve (fail) await expect(memberAccessPlugin.connect(dave).approve(pid)).to.be .reverted; // ADDRESS_ONE is still not a member - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); // Approve it (Bob) await expect(memberAccessPlugin.connect(bob).approve(pid)).to.not.be .reverted; // ADDRESS_ONE is now a member - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(true); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(true); // Now requesting for 0x2 - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) ).to.not.be.reverted; - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(false); // Dave cannot approve (fail) await expect(memberAccessPlugin.connect(dave).approve(pid)).to.be .reverted; // ADDRESS_TWO is still not a member - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(false); // Approve it (Carol) await expect(memberAccessPlugin.connect(carol).approve(pid)).to.not.be .reverted; // ADDRESS_TWO is now a member - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(true); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(true); }); it('Proposals should be unsettled after created', async () => { @@ -564,73 +563,73 @@ describe('Member Access Plugin', function () { // Proposed by a random wallet pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; let proposal = await memberAccessPlugin.getProposal(pid); expect(proposal.executed).to.eq(false); expect(proposal.parameters.minApprovals).to.eq(1); expect(await memberAccessPlugin.canExecute(pid)).to.eq(false); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); - await memberAccessPlugin.proposeNewMember('0x', dave.address); + await mainVotingPlugin.proposeAddMember('0x', dave.address); // Proposed by a (now) member pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_ONE) ).to.not.be.reverted; expect((await memberAccessPlugin.getProposal(pid)).executed).to.eq(false); expect(proposal.parameters.minApprovals).to.eq(1); expect(await memberAccessPlugin.canExecute(pid)).to.eq(false); - expect(await memberAccessPlugin.isMember(ADDRESS_ONE)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_ONE)).to.eq(false); // Proposed by an editor pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), ADDRESS_TWO) ).to.not.be.reverted; proposal = await memberAccessPlugin.getProposal(pid); expect(proposal.executed).to.eq(false); expect(proposal.parameters.minApprovals).to.eq(2); expect(await memberAccessPlugin.canExecute(pid)).to.eq(false); - expect(await memberAccessPlugin.isMember(ADDRESS_TWO)).to.eq(false); + expect(await mainVotingPlugin.isMember(ADDRESS_TWO)).to.eq(false); }); it('Only editors can reject new membership proposals', async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(3); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Reject it (Dave) => fail await expect(memberAccessPlugin.connect(dave).reject(pid)).to.be.reverted; // Still not a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Reject it (Bob) => success await expect(memberAccessPlugin.connect(bob).reject(pid)).to.not.be .reverted; // Still not a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Try to approve it (bob) => fail await expect(memberAccessPlugin.connect(bob).approve(pid)).to.be.reverted; @@ -640,32 +639,32 @@ describe('Member Access Plugin', function () { it("Proposals created by a non-editor need an editor's approval", async () => { expect(await mainVotingPlugin.addresslistLength()).to.eq(3); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; const proposal = await memberAccessPlugin.getProposal(pid); expect(proposal.executed).to.eq(false); expect(proposal.parameters.minApprovals).to.eq(1); expect(await memberAccessPlugin.canExecute(pid)).to.eq(false); - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Dave cannot await expect(memberAccessPlugin.connect(dave).approve(pid)).to.be .reverted; await expect(memberAccessPlugin.connect(dave).execute(pid)).to.be .reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); // Alice can await expect(memberAccessPlugin.connect(alice).approve(pid)).to.not.be .reverted; - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(true); }); it("Proposals created by an editor need another editor's approval", async () => { @@ -673,9 +672,9 @@ describe('Member Access Plugin', function () { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; const proposal = await memberAccessPlugin.getProposal(pid); @@ -690,9 +689,9 @@ describe('Member Access Plugin', function () { // Alice proposes a mew member pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; let proposal = await memberAccessPlugin.getProposal(pid); @@ -714,7 +713,7 @@ describe('Member Access Plugin', function () { expect(proposal.executed).to.eq(true); // Now Dave is a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(true); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(true); }); it('Memberships are rejected when the first non-proposer editor rejects', async () => { @@ -723,9 +722,9 @@ describe('Member Access Plugin', function () { // Alice proposes a mew member pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), dave.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), dave.address) ).to.not.be.reverted; expect((await memberAccessPlugin.getProposal(pid)).executed).to.eq(false); @@ -748,7 +747,7 @@ describe('Member Access Plugin', function () { expect((await memberAccessPlugin.getProposal(pid)).executed).to.eq(false); // Dave is still not a member - expect(await memberAccessPlugin.isMember(dave.address)).to.eq(false); + expect(await mainVotingPlugin.isMember(dave.address)).to.eq(false); }); }); @@ -759,9 +758,9 @@ describe('Member Access Plugin', function () { it('proposeNewMember should generate the right action list', async () => { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; const proposal = await memberAccessPlugin.getProposal(pid); @@ -777,17 +776,17 @@ describe('Member Access Plugin', function () { it('Proposing an existing member fails', async () => { await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), alice.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), alice.address) ) .to.be.revertedWithCustomError(memberAccessPlugin, 'AlreadyMember') .withArgs(alice.address); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), bob.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), bob.address) ) .to.be.revertedWithCustomError(memberAccessPlugin, 'AlreadyMember') .withArgs(bob.address); @@ -796,9 +795,9 @@ describe('Member Access Plugin', function () { it('Attempting to approve twice fails', async () => { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; await expect(memberAccessPlugin.approve(pid)).to.not.be.reverted; @@ -808,9 +807,9 @@ describe('Member Access Plugin', function () { it('Attempting to reject twice fails', async () => { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; await expect(memberAccessPlugin.reject(pid)).to.not.be.reverted; @@ -819,30 +818,30 @@ describe('Member Access Plugin', function () { it('Attempting to propose adding an existing member fails', async () => { await expect( - memberAccessPlugin + mainVotingPlugin .connect(carol) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), alice.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), alice.address) ).to.be.reverted; await expect( - memberAccessPlugin + mainVotingPlugin .connect(bob) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), alice.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), alice.address) ).to.be.reverted; await expect( - memberAccessPlugin + mainVotingPlugin .connect(alice) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), alice.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), alice.address) ).to.be.reverted; }); it('Rejected proposals cannot be approved', async () => { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; await expect(memberAccessPlugin.reject(pid)).to.not.be.reverted; @@ -852,9 +851,9 @@ describe('Member Access Plugin', function () { it('Rejected proposals cannot be executed', async () => { pid = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin + mainVotingPlugin .connect(dave) - .proposeNewMember(toUtf8Bytes('ipfs://1234'), carol.address) + .proposeAddMember(toUtf8Bytes('ipfs://1234'), carol.address) ).to.not.be.reverted; await expect(memberAccessPlugin.reject(pid)).to.not.be.reverted; @@ -862,7 +861,7 @@ describe('Member Access Plugin', function () { }); it('Fails to update the settings to use an incompatible main voting plugin', async () => { - const actionsWith = (targetAddr: string) => { + const actionsWith = (days: number) => { return [ { to: memberAccessPlugin.address, @@ -871,8 +870,7 @@ describe('Member Access Plugin', function () { 'updateMultisigSettings', [ { - proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: targetAddr, + proposalDuration: 60 * 60 * 24 * days, }, ] ), @@ -880,21 +878,12 @@ describe('Member Access Plugin', function () { ] as IDAO.ActionStruct[]; }; - await expect(dao.execute(ZERO_BYTES32, actionsWith(ADDRESS_ZERO), 0)).to - .be.reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(ADDRESS_ONE), 0)).to.be - .reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(ADDRESS_TWO), 0)).to.be + await expect(dao.execute(ZERO_BYTES32, actionsWith(1), 0)).to.be.reverted; + await expect(dao.execute(ZERO_BYTES32, actionsWith(4), 0)).to.be.reverted; + await expect(dao.execute(ZERO_BYTES32, actionsWith(10), 0)).to.be .reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(bob.address), 0)).to.be + await expect(dao.execute(ZERO_BYTES32, actionsWith(222), 0)).to.be .reverted; - - await expect( - dao.execute(ZERO_BYTES32, actionsWith(memberAccessPlugin.address), 0) - ).to.be.reverted; - await expect( - dao.execute(ZERO_BYTES32, actionsWith(mainVotingPlugin.address), 0) - ).to.not.be.reverted; }); it('Only the DAO can call the plugin to update the settings', async () => { @@ -902,25 +891,21 @@ describe('Member Access Plugin', function () { await expect( memberAccessPlugin.connect(alice).updateMultisigSettings({ proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.reverted; await expect( memberAccessPlugin.connect(bob).updateMultisigSettings({ proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.reverted; await expect( memberAccessPlugin.connect(carol).updateMultisigSettings({ proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.reverted; await expect( memberAccessPlugin.connect(dave).updateMultisigSettings({ proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.reverted; @@ -934,7 +919,6 @@ describe('Member Access Plugin', function () { [ { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }, ] ), @@ -985,13 +969,15 @@ describe('Member Access Plugin', function () { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }) ).to.be.revertedWith('Initializable: contract is already initialized'); await expect( - mainVotingPlugin.initialize(dao.address, defaultMainVotingSettings, [ - alice.address, - ]) + mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + memberAccessPlugin.address + ) ).to.be.revertedWith('Initializable: contract is already initialized'); await expect( spacePlugin.initialize( @@ -1007,7 +993,6 @@ describe('Member Access Plugin', function () { new MemberAccessPlugin__factory(alice) ); const multisigSettings: MemberAccessPlugin.MultisigSettingsStruct = { - mainVotingPlugin: mainVotingPlugin.address, proposalDuration: 60 * 60 * 24 * 5, }; @@ -1071,7 +1056,6 @@ describe('Member Access Plugin', function () { ); const multisigSettings = { proposalDuration: 60 * 60 * 24 * 5, - mainVotingPlugin: mainVotingPlugin.address, }; await expect( @@ -1087,28 +1071,26 @@ describe('Member Access Plugin', function () { const pc = await memberAccessPlugin.proposalCount(); await expect( - memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address) + mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address) ).not.to.be.reverted; expect(await memberAccessPlugin.proposalCount()).to.equal(pc.add(1)); }); it('creates unique proposal IDs for each proposal', async () => { - const proposalId0 = - await memberAccessPlugin.callStatic.proposeNewMember( - EMPTY_DATA, - carol.address - ); + const proposalId0 = await mainVotingPlugin.callStatic.proposeAddMember( + EMPTY_DATA, + carol.address + ); // create a new proposal for the proposalCounter to be incremented await expect( - memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address) + mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address) ).not.to.be.reverted; - const proposalId1 = - await memberAccessPlugin.callStatic.proposeNewMember( - EMPTY_DATA, - dave.address - ); + const proposalId1 = await mainVotingPlugin.callStatic.proposeAddMember( + EMPTY_DATA, + dave.address + ); expect(proposalId0).to.equal(1); expect(proposalId1).to.equal(2); @@ -1118,7 +1100,7 @@ describe('Member Access Plugin', function () { it('emits the `ProposalCreated` event', async () => { await expect( - memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address) + mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address) ).to.emit(memberAccessPlugin, 'ProposalCreated'); }); @@ -1141,7 +1123,6 @@ describe('Member Access Plugin', function () { 'updateMultisigSettings', [ { - mainVotingPlugin: mainVotingPlugin.address, proposalDuration: 60 * 60 * 24 * 5, }, ] @@ -1151,7 +1132,7 @@ describe('Member Access Plugin', function () { 0 ); await expect( - memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address) + mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address) ) .to.revertedWithCustomError( memberAccessPlugin, @@ -1168,13 +1149,13 @@ describe('Member Access Plugin', function () { await proposeNewEditor(bob.address); // have 2 editors await mineBlock(); - expect(await memberAccessPlugin.isEditor(alice.address)).to.be.true; - expect(await memberAccessPlugin.isEditor(bob.address)).to.be.true; - expect(await memberAccessPlugin.isEditor(carol.address)).to.be.false; + expect(await mainVotingPlugin.isEditor(alice.address)).to.be.true; + expect(await mainVotingPlugin.isEditor(bob.address)).to.be.true; + expect(await mainVotingPlugin.isEditor(carol.address)).to.be.false; // Alice approves pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); }); it('returns `false` if the proposal is already executed', async () => { @@ -1188,8 +1169,7 @@ describe('Member Access Plugin', function () { }); it('returns `false` if the approver is not an editor', async () => { - expect(await memberAccessPlugin.isEditor(signers[9].address)).to.be - .false; + expect(await mainVotingPlugin.isEditor(signers[9].address)).to.be.false; expect(await memberAccessPlugin.canApprove(pid, signers[9].address)).to .be.false; @@ -1210,7 +1190,7 @@ describe('Member Access Plugin', function () { it('returns `false` if the proposal is settled', async () => { pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); expect(await memberAccessPlugin.canApprove(pid, bob.address)).to.be .true; @@ -1229,7 +1209,7 @@ describe('Member Access Plugin', function () { // Carol is a member pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); }); it("returns `false` if user hasn't approved yet", async () => { @@ -1251,7 +1231,7 @@ describe('Member Access Plugin', function () { // Alice approves pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); }); it('reverts when approving multiple times', async () => { @@ -1299,13 +1279,13 @@ describe('Member Access Plugin', function () { await proposeNewEditor(bob.address); // have 2 editors await mineBlock(); - expect(await memberAccessPlugin.isEditor(alice.address)).to.be.true; - expect(await memberAccessPlugin.isEditor(bob.address)).to.be.true; - expect(await memberAccessPlugin.isEditor(carol.address)).to.be.false; + expect(await mainVotingPlugin.isEditor(alice.address)).to.be.true; + expect(await mainVotingPlugin.isEditor(bob.address)).to.be.true; + expect(await mainVotingPlugin.isEditor(carol.address)).to.be.false; // Alice approves pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); }); it('returns `false` if the proposal has not reached the minimum approval yet', async () => { @@ -1338,7 +1318,7 @@ describe('Member Access Plugin', function () { // Alice approves pid = await memberAccessPlugin.proposalCount(); - await memberAccessPlugin.proposeNewMember(EMPTY_DATA, carol.address); + await mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address); }); it('reverts if the minimum approval is not met', async () => { From a1b0f450314386cc857b6bc1cfd6eb0013139396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 4 Jun 2024 19:10:40 +0200 Subject: [PATCH 05/14] Testing work in progress --- .../src/governance/MainVotingPlugin.sol | 8 +++- .../src/governance/MemberAccessPlugin.sol | 22 +++++---- .../test/unit-testing/main-voting-plugin.ts | 45 +++++++++++++++---- .../test/unit-testing/member-access-plugin.ts | 35 ++++----------- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/packages/contracts/src/governance/MainVotingPlugin.sol b/packages/contracts/src/governance/MainVotingPlugin.sol index 88f5bc9..3b9b4bf 100644 --- a/packages/contracts/src/governance/MainVotingPlugin.sol +++ b/packages/contracts/src/governance/MainVotingPlugin.sol @@ -10,7 +10,7 @@ import {MajorityVotingBase} from "./base/MajorityVotingBase.sol"; import {IMembers} from "../base/IMembers.sol"; import {IEditors} from "../base/IEditors.sol"; import {Addresslist} from "./base/Addresslist.sol"; -import {MemberAccessPlugin} from "./MemberAccessPlugin.sol"; +import {MemberAccessPlugin, MEMBER_ACCESS_INTERFACE_ID} from "./MemberAccessPlugin.sol"; import {SpacePlugin} from "../space/SpacePlugin.sol"; // The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. @@ -74,6 +74,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers /// @notice Raised when a content proposal is called with empty data error EmptyContent(); + /// @notice Thrown when the given contract doesn't support a required interface. + error InvalidInterface(address); + /// @notice Raised when a non-editor attempts to call a restricted function. error Unauthorized(); @@ -111,6 +114,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers _addAddresses(_initialEditors); emit EditorsAdded(_initialEditors); + if (!_memberAccessPlugin.supportsInterface(MEMBER_ACCESS_INTERFACE_ID)) { + revert InvalidInterface(address(_memberAccessPlugin)); + } memberAccessPlugin = _memberAccessPlugin; } diff --git a/packages/contracts/src/governance/MemberAccessPlugin.sol b/packages/contracts/src/governance/MemberAccessPlugin.sol index fc4d871..db73b6e 100644 --- a/packages/contracts/src/governance/MemberAccessPlugin.sol +++ b/packages/contracts/src/governance/MemberAccessPlugin.sol @@ -102,7 +102,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade error ProposalExecutionForbidden(uint256 proposalId); /// @notice Thrown when called from an incompatible contract. - error InvalidCallerInterface(); + error InvalidInterface(); /// @notice Emitted when a proposal is approved by an editor. /// @param proposalId The ID of the proposal. @@ -167,7 +167,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade !MainVotingPlugin(msg.sender).supportsInterface(type(IEditors).interfaceId) || !MainVotingPlugin(msg.sender).supportsInterface(type(Addresslist).interfaceId) ) { - revert InvalidCallerInterface(); + revert InvalidInterface(); } // Build the list of actions @@ -233,17 +233,22 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade } // If the creator is an editor, we assume that the editor approves - approve(proposalId); + _approve(proposalId, _proposer); } else { proposal_.parameters.minApprovals = MIN_APPROVALS_WHEN_CREATED_BY_NON_EDITOR; } } /// @inheritdoc IMultisig - /// @dev The second parameter is left empty to keep compatibility with the existing multisig interface + /// @param _proposalId The Id of the proposal to approve. function approve(uint256 _proposalId) public { - if (!_canApprove(_proposalId, msg.sender)) { - revert ApprovalCastForbidden(_proposalId, msg.sender); + _approve(_proposalId, msg.sender); + } + + /// @notice Internal implementation, allowing proposeAddMember() to specify the proposer. + function _approve(uint256 _proposalId, address _approver) internal { + if (!_canApprove(_proposalId, _approver)) { + revert ApprovalCastForbidden(_proposalId, _approver); } Proposal storage proposal_ = proposals[_proposalId]; @@ -254,9 +259,9 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade proposal_.approvals += 1; } - proposal_.approvers[msg.sender] = true; + proposal_.approvers[_approver] = true; - emit Approved({proposalId: _proposalId, editor: msg.sender}); + emit Approved({proposalId: _proposalId, editor: _approver}); if (_canExecute(_proposalId)) { _execute(_proposalId); @@ -264,6 +269,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade } /// @notice Rejects the given proposal immediately. + /// @param _proposalId The Id of the proposal to reject. function reject(uint256 _proposalId) public { if (!_canApprove(_proposalId, msg.sender)) { revert ApprovalCastForbidden(_proposalId, msg.sender); diff --git a/packages/contracts/test/unit-testing/main-voting-plugin.ts b/packages/contracts/test/unit-testing/main-voting-plugin.ts index 34e8598..52c6a08 100644 --- a/packages/contracts/test/unit-testing/main-voting-plugin.ts +++ b/packages/contracts/test/unit-testing/main-voting-plugin.ts @@ -156,10 +156,20 @@ describe('Main Voting Plugin', function () { describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { await expect( - memberAccessPlugin.initialize(dao.address, { - proposalDuration: 60 * 60 * 24 * 5, - }) + mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + memberAccessPlugin.address + ) ).to.be.revertedWith('Initializable: contract is already initialized'); + }); + + it('Fails to initialize with an incompatible main voting plugin', async () => { + // ok + mainVotingPlugin = await deployWithProxy( + new MainVotingPlugin__factory(alice) + ); await expect( mainVotingPlugin.initialize( dao.address, @@ -167,14 +177,33 @@ describe('Main Voting Plugin', function () { [alice.address], memberAccessPlugin.address ) - ).to.be.revertedWith('Initializable: contract is already initialized'); + ).to.not.be.reverted; + + // not ok + mainVotingPlugin = await deployWithProxy( + new MainVotingPlugin__factory(alice) + ); await expect( - spacePlugin.initialize( + mainVotingPlugin.initialize( dao.address, - defaultInput.contentUri, - ADDRESS_ZERO + defaultMainVotingSettings, + [alice.address], + bob.address ) - ).to.be.revertedWith('Initializable: contract is already initialized'); + ).to.be.reverted; + + // not ok + mainVotingPlugin = await deployWithProxy( + new MainVotingPlugin__factory(alice) + ); + await expect( + mainVotingPlugin.initialize( + dao.address, + defaultMainVotingSettings, + [alice.address], + spacePlugin.address + ) + ).to.be.reverted; }); it('The plugin has one editor after created', async () => { diff --git a/packages/contracts/test/unit-testing/member-access-plugin.ts b/packages/contracts/test/unit-testing/member-access-plugin.ts index e766723..9a4a9e0 100644 --- a/packages/contracts/test/unit-testing/member-access-plugin.ts +++ b/packages/contracts/test/unit-testing/member-access-plugin.ts @@ -28,6 +28,7 @@ import { EMPTY_DATA, EXECUTE_PERMISSION_ID, mineBlock, + PROPOSER_PERMISSION_ID, ROOT_PERMISSION_ID, UPDATE_ADDRESSES_PERMISSION_ID, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, @@ -142,6 +143,12 @@ describe('Member Access Plugin', function () { ); // The DAO is ROOT on itself await dao.grant(dao.address, dao.address, ROOT_PERMISSION_ID); + // The plugin can propose members on the member access helper + await dao.grant( + memberAccessPlugin.address, + mainVotingPlugin.address, + PROPOSER_PERMISSION_ID + ); // Alice can make the DAO execute arbitrary stuff (test) await dao.grant(dao.address, alice.address, EXECUTE_PERMISSION_ID); @@ -153,36 +160,12 @@ describe('Member Access Plugin', function () { }); describe('initialize', () => { - it('Fails to initialize with an incompatible main voting plugin', async () => { - // ok - memberAccessPlugin = await deployWithProxy( - new MemberAccessPlugin__factory(alice) - ); - await expect( - memberAccessPlugin.initialize(dao.address, { - proposalDuration: 60 * 60 * 24 * 5, - }) - ).to.not.be.reverted; - - // not ok - memberAccessPlugin = await deployWithProxy( - new MemberAccessPlugin__factory(alice) - ); - await expect( - memberAccessPlugin.initialize(dao.address, { - proposalDuration: 60 * 60 * 24 * 5, - }) - ).to.be.reverted; - - // not ok - memberAccessPlugin = await deployWithProxy( - new MemberAccessPlugin__factory(alice) - ); + it('reverts if trying to re-initialize', async () => { await expect( memberAccessPlugin.initialize(dao.address, { proposalDuration: 60 * 60 * 24 * 5, }) - ).to.be.reverted; + ).to.be.revertedWith('Initializable: contract is already initialized'); }); }); From 4d6d9ef6fc8450c7b6934576502fe94d9dd8494e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 4 Jun 2024 19:22:29 +0200 Subject: [PATCH 06/14] Work in progress --- .../contracts/src/governance/GovernancePluginsSetup.sol | 3 ++- .../test/unit-testing/governance-plugins-setup.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/contracts/src/governance/GovernancePluginsSetup.sol b/packages/contracts/src/governance/GovernancePluginsSetup.sol index 639c811..a33e0bb 100644 --- a/packages/contracts/src/governance/GovernancePluginsSetup.sol +++ b/packages/contracts/src/governance/GovernancePluginsSetup.sol @@ -221,7 +221,8 @@ contract GovernancePluginsSetup is PluginSetup { where: _memberAccessPlugin, who: _payload.plugin, condition: address(0), - permissionId: MemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + permissionId: MemberAccessPlugin(memberAccessPluginImplementation) + .PROPOSER_PERMISSION_ID() }); // The plugin can no longer execute on the DAO diff --git a/packages/contracts/test/unit-testing/governance-plugins-setup.ts b/packages/contracts/test/unit-testing/governance-plugins-setup.ts index 661cd7e..d838216 100644 --- a/packages/contracts/test/unit-testing/governance-plugins-setup.ts +++ b/packages/contracts/test/unit-testing/governance-plugins-setup.ts @@ -304,8 +304,8 @@ describe('Governance Plugins Setup', function () { ], [ Operation.Revoke, - memberAccessPlugin, - mainVotingPlugin, + memberAccessPlugin.address, + mainVotingPlugin.address, NO_CONDITION, PROPOSER_PERMISSION_ID, ], @@ -372,8 +372,8 @@ describe('Governance Plugins Setup', function () { ], [ Operation.Revoke, - memberAccessPlugin, - mainVotingPlugin, + memberAccessPlugin.address, + mainVotingPlugin.address, NO_CONDITION, PROPOSER_PERMISSION_ID, ], From 80328263325709f1d56515025c8969f3fa70158e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:17:42 +0200 Subject: [PATCH 07/14] Adapting tests --- packages/contracts/hardhat.config.ts | 1 + packages/contracts/package.json | 3 +- .../src/test/TestGovernancePluginsSetup.sol | 8 +- yarn.lock | 454 +++++------------- 4 files changed, 137 insertions(+), 329 deletions(-) diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index f0bd40b..eccc8ad 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -7,6 +7,7 @@ import '@typechain/hardhat'; import {config as dotenvConfig} from 'dotenv'; import 'hardhat-deploy'; import 'hardhat-gas-reporter'; +import 'hardhat-tracer'; import {extendEnvironment, HardhatUserConfig} from 'hardhat/config'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import type {NetworkUserConfig} from 'hardhat/types'; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 519a4a2..975b741 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -25,9 +25,10 @@ "@types/node": "^18.11.9", "chai": "^4.3.7", "ethers": "^5.7.2", - "hardhat": "^2.13.1", + "hardhat": "^2.22.5", "hardhat-deploy": "^0.11.37", "hardhat-gas-reporter": "^1.0.9", + "hardhat-tracer": "^3.0.1", "mocha": "^10.1.0", "solhint": "^3.4.0", "solhint-plugin-prettier": "^0.0.5", diff --git a/packages/contracts/src/test/TestGovernancePluginsSetup.sol b/packages/contracts/src/test/TestGovernancePluginsSetup.sol index 2b247c5..d9faf1f 100644 --- a/packages/contracts/src/test/TestGovernancePluginsSetup.sol +++ b/packages/contracts/src/test/TestGovernancePluginsSetup.sol @@ -107,7 +107,7 @@ contract TestGovernancePluginsSetup is PluginSetup { // The member access plugin needs to execute on the DAO permissions[3] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.GrantWithCondition, + operation: PermissionLib.Operation.Grant, where: _memberAccessPlugin, who: mainVotingPlugin, condition: PermissionLib.NO_CONDITION, @@ -254,7 +254,7 @@ contract TestGovernancePluginsSetup is PluginSetup { operation: PermissionLib.Operation.Revoke, where: _dao, who: _memberAccessPlugin, - condition: address(0), + condition: PermissionLib.NO_CONDITION, permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); // The DAO can no longer update the plugin settings @@ -262,7 +262,7 @@ contract TestGovernancePluginsSetup is PluginSetup { operation: PermissionLib.Operation.Revoke, where: _memberAccessPlugin, who: _dao, - condition: address(0), + condition: PermissionLib.NO_CONDITION, permissionId: TestMemberAccessPlugin(memberAccessPluginImplementation()) .UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() }); @@ -274,7 +274,7 @@ contract TestGovernancePluginsSetup is PluginSetup { operation: PermissionLib.Operation.Revoke, where: _dao, who: _pluginUpgrader, - condition: address(0), + condition: PermissionLib.NO_CONDITION, permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() }); } diff --git a/yarn.lock b/yarn.lock index 082ec0e..2649d35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -197,42 +197,6 @@ "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" -"@chainsafe/as-sha256@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" - integrity sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg== - -"@chainsafe/persistent-merkle-tree@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz#4c9ee80cc57cd3be7208d98c40014ad38f36f7ff" - integrity sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ== - dependencies: - "@chainsafe/as-sha256" "^0.3.1" - -"@chainsafe/persistent-merkle-tree@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz#2b4a62c9489a5739dedd197250d8d2f5427e9f63" - integrity sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw== - dependencies: - "@chainsafe/as-sha256" "^0.3.1" - -"@chainsafe/ssz@^0.10.0": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.10.2.tgz#c782929e1bb25fec66ba72e75934b31fd087579e" - integrity sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg== - dependencies: - "@chainsafe/as-sha256" "^0.3.1" - "@chainsafe/persistent-merkle-tree" "^0.5.0" - -"@chainsafe/ssz@^0.9.2": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.9.4.tgz#696a8db46d6975b600f8309ad3a12f7c0e310497" - integrity sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ== - dependencies: - "@chainsafe/as-sha256" "^0.3.1" - "@chainsafe/persistent-merkle-tree" "^0.4.2" - case "^1.6.3" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -554,7 +518,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -833,139 +797,83 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/ethereumjs-block@5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz#6f89664f55febbd723195b6d0974773d29ee133d" - integrity sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw== - dependencies: - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-trie" "6.0.1" - "@nomicfoundation/ethereumjs-tx" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - ethereum-cryptography "0.1.3" - ethers "^5.7.1" +"@nomicfoundation/edr-darwin-arm64@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.4.0.tgz#bbb43f0e01f40839b0bd38c2c443cb6910ae955f" + integrity sha512-7+rraFk9tCqvfemv9Ita5vTlSBAeO/S5aDKOgGRgYt0JEKZlrX161nDW6UfzMPxWl9GOLEDUzCEaYuNmXseUlg== -"@nomicfoundation/ethereumjs-blockchain@7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz#80e0bd3535bfeb9baa29836b6f25123dab06a726" - integrity sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A== - dependencies: - "@nomicfoundation/ethereumjs-block" "5.0.1" - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-ethash" "3.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-trie" "6.0.1" - "@nomicfoundation/ethereumjs-tx" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - abstract-level "^1.0.3" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - level "^8.0.0" - lru-cache "^5.1.1" - memory-level "^1.0.0" +"@nomicfoundation/edr-darwin-x64@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.4.0.tgz#b1ffcd9142418fd8498de34a7336b3f977907c86" + integrity sha512-+Hrc0mP9L6vhICJSfyGo/2taOToy1AIzVZawO3lU8Lf7oDQXfhQ4UkZnkWAs9SVu1eUwHUGGGE0qB8644piYgg== -"@nomicfoundation/ethereumjs-common@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz#4702d82df35b07b5407583b54a45bf728e46a2f0" - integrity sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g== - dependencies: - "@nomicfoundation/ethereumjs-util" "9.0.1" - crc-32 "^1.2.0" +"@nomicfoundation/edr-linux-arm64-gnu@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.4.0.tgz#8173d16d4f6f2b3e82ba7096d2a1ea3619d8bfa7" + integrity sha512-4HUDMchNClQrVRfVTqBeSX92hM/3khCgpZkXP52qrnJPqgbdCxosOehlQYZ65wu0b/kaaZSyvACgvCLSQ5oSzQ== -"@nomicfoundation/ethereumjs-ethash@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz#65ca494d53e71e8415c9a49ef48bc921c538fc41" - integrity sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w== - dependencies: - "@nomicfoundation/ethereumjs-block" "5.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - abstract-level "^1.0.3" - bigint-crypto-utils "^3.0.23" - ethereum-cryptography "0.1.3" +"@nomicfoundation/edr-linux-arm64-musl@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.4.0.tgz#b1ce293a7c3e0d9f70391e1aef1a82b83b997567" + integrity sha512-D4J935ZRL8xfnP3zIFlCI9jXInJ0loDUkCTLeCEbOf2uuDumWDghKNQlF1itUS+EHaR1pFVBbuwqq8hVK0dASg== -"@nomicfoundation/ethereumjs-evm@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz#f35681e203363f69ce2b3d3bf9f44d4e883ca1f1" - integrity sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ== - dependencies: - "@ethersproject/providers" "^5.7.1" - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-tx" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - mcl-wasm "^0.7.1" - rustbn.js "~0.2.0" +"@nomicfoundation/edr-linux-x64-gnu@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.4.0.tgz#4c12c4e4bfd3d837f5663ad7cbf7cb6d5634ef83" + integrity sha512-6x7HPy+uN5Cb9N77e2XMmT6+QSJ+7mRbHnhkGJ8jm4cZvWuj2Io7npOaeHQ3YHK+TiQpTnlbkjoOIpEwpY3XZA== -"@nomicfoundation/ethereumjs-rlp@5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz#0b30c1cf77d125d390408e391c4bb5291ef43c28" - integrity sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ== +"@nomicfoundation/edr-linux-x64-musl@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.4.0.tgz#8842004aa1a47c504f10863687da28b65dca7baa" + integrity sha512-3HFIJSXgyubOiaN4MWGXx2xhTnhwlJk0PiSYNf9+L/fjBtcRkb2nM910ZJHTvqCb6OT98cUnaKuAYdXIW2amgw== -"@nomicfoundation/ethereumjs-statemanager@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz#8824a97938db4471911e2d2f140f79195def5935" - integrity sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ== - dependencies: - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - ethers "^5.7.1" - js-sdsl "^4.1.4" +"@nomicfoundation/edr-win32-x64-msvc@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.4.0.tgz#29d8bbb2edf9912a95f5453855cf17cdcb269957" + integrity sha512-CP4GsllEfXEz+lidcGYxKe5rDJ60TM5/blB5z/04ELVvw6/CK9eLcYeku7HV0jvV7VE6dADYKSdQyUkvd0El+A== -"@nomicfoundation/ethereumjs-trie@6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz#662c55f6b50659fd4b22ea9f806a7401cafb7717" - integrity sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA== +"@nomicfoundation/edr@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.4.0.tgz#4895ecb6ef321136db837458949c37cce4a29459" + integrity sha512-T96DMSogO8TCdbKKctvxfsDljbhFOUKWc9fHJhSeUh71EEho2qR4951LKQF7t7UWEzguVYh/idQr5L/E3QeaMw== + dependencies: + "@nomicfoundation/edr-darwin-arm64" "0.4.0" + "@nomicfoundation/edr-darwin-x64" "0.4.0" + "@nomicfoundation/edr-linux-arm64-gnu" "0.4.0" + "@nomicfoundation/edr-linux-arm64-musl" "0.4.0" + "@nomicfoundation/edr-linux-x64-gnu" "0.4.0" + "@nomicfoundation/edr-linux-x64-musl" "0.4.0" + "@nomicfoundation/edr-win32-x64-msvc" "0.4.0" + +"@nomicfoundation/ethereumjs-common@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz#9901f513af2d4802da87c66d6f255b510bef5acb" + integrity sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg== dependencies: - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - "@types/readable-stream" "^2.3.13" - ethereum-cryptography "0.1.3" - readable-stream "^3.6.0" + "@nomicfoundation/ethereumjs-util" "9.0.4" -"@nomicfoundation/ethereumjs-tx@5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz#7629dc2036b4a33c34e9f0a592b43227ef4f0c7d" - integrity sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w== - dependencies: - "@chainsafe/ssz" "^0.9.2" - "@ethersproject/providers" "^5.7.2" - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - ethereum-cryptography "0.1.3" +"@nomicfoundation/ethereumjs-rlp@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz#66c95256fc3c909f6fb18f6a586475fc9762fa30" + integrity sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw== -"@nomicfoundation/ethereumjs-util@9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz#530cda8bae33f8b5020a8f199ed1d0a2ce48ec89" - integrity sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA== +"@nomicfoundation/ethereumjs-tx@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz#b0ceb58c98cc34367d40a30d255d6315b2f456da" + integrity sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw== dependencies: - "@chainsafe/ssz" "^0.10.0" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-rlp" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-vm@7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz#7d035e0993bcad10716c8b36e61dfb87fa3ca05f" - integrity sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ== - dependencies: - "@nomicfoundation/ethereumjs-block" "5.0.1" - "@nomicfoundation/ethereumjs-blockchain" "7.0.1" - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-evm" "2.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-statemanager" "2.0.1" - "@nomicfoundation/ethereumjs-trie" "6.0.1" - "@nomicfoundation/ethereumjs-tx" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - debug "^4.3.3" +"@nomicfoundation/ethereumjs-util@9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz#84c5274e82018b154244c877b76bc049a4ed7b38" + integrity sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "5.0.4" ethereum-cryptography "0.1.3" - mcl-wasm "^0.7.1" - rustbn.js "~0.2.0" "@nomicfoundation/hardhat-chai-matchers@^1.0.6": version "1.0.6" @@ -1611,14 +1519,6 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/readable-stream@^2.3.13": - version "2.3.15" - resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.15.tgz#3d79c9ceb1b6a57d5f6e6976f489b9b5384321ae" - integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ== - dependencies: - "@types/node" "*" - safe-buffer "~5.1.1" - "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -1732,31 +1632,11 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - abortcontroller-polyfill@^1.7.3: version "1.7.5" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== -abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" - integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA== - dependencies: - buffer "^6.0.3" - catering "^2.1.0" - is-buffer "^2.0.5" - level-supports "^4.0.0" - level-transcoder "^1.0.1" - module-error "^1.0.1" - queue-microtask "^1.2.3" - accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -1846,6 +1726,13 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-colors@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" @@ -2111,11 +1998,6 @@ big.js@^6.0.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== -bigint-crypto-utils@^3.0.23: - version "3.2.2" - resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz#e30a49ec38357c6981cd3da5aaa6480b1f752ee4" - integrity sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw== - bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" @@ -2197,6 +2079,20 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2224,16 +2120,6 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browser-level@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-1.0.1.tgz#36e8c3183d0fe1c405239792faaab5f315871011" - integrity sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ== - dependencies: - abstract-level "^1.0.2" - catering "^2.1.1" - module-error "^1.0.2" - run-parallel-limit "^1.1.0" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -2291,7 +2177,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@6.0.3, buffer@^6.0.3: +buffer@6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -2385,26 +2271,16 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -case@^1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" - integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== - caseless@^0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -catering@^2.1.0, catering@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" - integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== - cbor@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c" @@ -2580,22 +2456,16 @@ class-is@^1.1.0: resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== -classic-level@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-1.3.0.tgz#5e36680e01dc6b271775c093f2150844c5edd5c8" - integrity sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg== - dependencies: - abstract-level "^1.0.2" - catering "^2.1.0" - module-error "^1.0.1" - napi-macros "^2.2.2" - node-gyp-build "^4.3.0" - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + cli-table3@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" @@ -2937,7 +2807,7 @@ debug@3.2.6: dependencies: ms "^2.1.1" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3615,7 +3485,7 @@ ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.0.13, ethers@^5.5.3, ethers@^5.6.2, ethers@^5.7.1, ethers@^5.7.2: +ethers@^5.0.13, ethers@^5.5.3, ethers@^5.6.1, ethers@^5.6.2, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3667,11 +3537,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -4066,11 +3931,6 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -4446,31 +4306,35 @@ hardhat-gas-reporter@^1.0.9: eth-gas-reporter "^0.2.25" sha1 "^1.1.1" -hardhat@^2.13.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.15.0.tgz#0cacb2b44c4c4651aa8ab649fef12804848b0267" - integrity sha512-cC9tM/N10YaES04zPOp7yR13iX3YibqaNmi0//Ep40Nt9ELIJx3kFpQmucur0PAIfXYpGnw5RuXHNLkxpnVHEw== +hardhat-tracer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hardhat-tracer/-/hardhat-tracer-3.0.1.tgz#fc5b72d45b074da0833bb2cd7a962524b9879de3" + integrity sha512-uER+fGjAUxEaJE0jGZxmBufe2ODyxFSlW820ybP001RvSDeRWGEqyLEPqHS0nZe+C9uHORqK1vk/54zbHnm+2Q== + dependencies: + chalk "^4.1.2" + debug "^4.3.4" + ethers "^5.6.1" + semver "^7.6.2" + +hardhat@^2.22.5: + version "2.22.5" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.5.tgz#7e1a4311fa9e34a1cfe337784eae06706f6469a5" + integrity sha512-9Zq+HonbXCSy6/a13GY1cgHglQRfh4qkzmj1tpPlhxJDwNVnhxlReV6K7hCWFKlOrV13EQwsdcD0rjcaQKWRZw== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/ethereumjs-block" "5.0.1" - "@nomicfoundation/ethereumjs-blockchain" "7.0.1" - "@nomicfoundation/ethereumjs-common" "4.0.1" - "@nomicfoundation/ethereumjs-evm" "2.0.1" - "@nomicfoundation/ethereumjs-rlp" "5.0.1" - "@nomicfoundation/ethereumjs-statemanager" "2.0.1" - "@nomicfoundation/ethereumjs-trie" "6.0.1" - "@nomicfoundation/ethereumjs-tx" "5.0.1" - "@nomicfoundation/ethereumjs-util" "9.0.1" - "@nomicfoundation/ethereumjs-vm" "7.0.1" + "@nomicfoundation/edr" "^0.4.0" + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-tx" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" - abort-controller "^3.0.0" adm-zip "^0.4.16" aggregate-error "^3.0.0" ansi-escapes "^4.3.0" + boxen "^5.1.2" chalk "^2.4.2" chokidar "^3.4.0" ci-info "^2.0.0" @@ -4490,7 +4354,6 @@ hardhat@^2.13.1: mnemonist "^0.38.0" mocha "^10.0.0" p-map "^4.0.0" - qs "^6.7.0" raw-body "^2.4.1" resolve "1.17.0" semver "^6.3.0" @@ -4851,7 +4714,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^2.0.5, is-buffer@~2.0.3: +is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -5076,11 +4939,6 @@ js-cookie@^2.2.1: resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== -js-sdsl@^4.1.4: - version "4.4.1" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.1.tgz#9e3c7b566d8d9a7e1fe8fc26d00b5ab0f8918ab3" - integrity sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA== - js-sha3@0.5.7, js-sha3@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" @@ -5237,27 +5095,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -level-supports@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" - integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA== - -level-transcoder@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" - integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== - dependencies: - buffer "^6.0.3" - module-error "^1.0.1" - -level@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/level/-/level-8.0.0.tgz#41b4c515dabe28212a3e881b61c161ffead14394" - integrity sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ== - dependencies: - browser-level "^1.0.1" - classic-level "^1.2.0" - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -5382,13 +5219,6 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -5428,11 +5258,6 @@ match-all@^1.2.6: resolved "https://registry.yarnpkg.com/match-all/-/match-all-1.2.6.tgz#66d276ad6b49655551e63d3a6ee53e8be0566f8d" integrity sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ== -mcl-wasm@^0.7.1: - version "0.7.9" - resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz#c1588ce90042a8700c3b60e40efb339fc07ab87f" - integrity sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ== - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5447,15 +5272,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memory-level@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692" - integrity sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og== - dependencies: - abstract-level "^1.0.0" - functional-red-black-tree "^1.0.1" - module-error "^1.0.1" - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -5718,11 +5534,6 @@ mock-fs@^4.1.0: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== -module-error@^1.0.1, module-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" - integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5807,11 +5618,6 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -napi-macros@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044" - integrity sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g== - natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -6413,7 +6219,7 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.4.0, qs@^6.7.0, qs@^6.9.4: +qs@^6.4.0, qs@^6.9.4: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -6434,7 +6240,7 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -queue-microtask@^1.2.2, queue-microtask@^1.2.3: +queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -6769,13 +6575,6 @@ rollup@^2.70.1: optionalDependencies: fsevents "~2.3.2" -run-parallel-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" - integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== - dependencies: - queue-microtask "^1.2.2" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -6783,11 +6582,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rustbn.js@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" - integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== - safe-array-concat@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" @@ -6892,6 +6686,11 @@ semver@^7.3.8: dependencies: lru-cache "^6.0.0" +semver@^7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -7267,7 +7066,7 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -8280,6 +8079,13 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" @@ -8428,7 +8234,7 @@ yaeti@^0.0.6: resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: +yallist@^3.0.0, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From b8a7ef520fa8bd4cb4a1e09621eae9bd3bd3ffaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:13:23 +0200 Subject: [PATCH 08/14] Adapting more tests --- .../member-access-condition.ts | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/packages/contracts/test/integration-testing/member-access-condition.ts b/packages/contracts/test/integration-testing/member-access-condition.ts index 2e1f0e2..bbed7a0 100644 --- a/packages/contracts/test/integration-testing/member-access-condition.ts +++ b/packages/contracts/test/integration-testing/member-access-condition.ts @@ -4,8 +4,8 @@ import { MainVotingPlugin, MainVotingPlugin__factory, MajorityVotingBase, - TestMemberAccessPlugin, - TestMemberAccessPlugin__factory, + MemberAccessPlugin, + MemberAccessPlugin__factory, PluginRepo, } from '../../typechain'; import {PluginSetupRefStruct} from '../../typechain/@aragon/osx/framework/dao/DAOFactory'; @@ -16,13 +16,6 @@ import { } from '../../utils/helpers'; import {installPlugin} from '../helpers/setup'; import {deployTestDao} from '../helpers/test-dao'; -import { - EXECUTE_PERMISSION_ID, - ROOT_PERMISSION_ID, - UPGRADE_PLUGIN_PERMISSION_ID, - ONE_BYTES32, - ADDRESS_ONE, -} from '../unit-testing/common'; import { DAO, PluginRepo__factory, @@ -62,7 +55,7 @@ describe('Member Access Condition E2E', () => { let pluginSetup: TestGovernancePluginsSetup; let gpsFactory: TestGovernancePluginsSetup__factory; let mainVotingPlugin: MainVotingPlugin; - let memberAccessPlugin: TestMemberAccessPlugin; + // let memberAccessPlugin: MemberAccessPlugin; before(async () => { [deployer, pluginUpgrader, alice, bob] = await ethers.getSigners(); @@ -153,22 +146,18 @@ describe('Member Access Condition E2E', () => { installation.preparedEvent.args.plugin, deployer ); - memberAccessPlugin = TestMemberAccessPlugin__factory.connect( - installation.preparedEvent.args.preparedSetupData.helpers[0], - deployer - ); + // memberAccessPlugin = MemberAccessPlugin__factory.connect( + // installation.preparedEvent.args.preparedSetupData.helpers[0], + // deployer + // ); }); it('Executing a proposal to add membership works', async () => { expect(await mainVotingPlugin.isMember(alice.address)).to.eq(false); + expect(await mainVotingPlugin.isEditor(deployer.address)).to.eq(true); - await expect( - memberAccessPlugin.devProposeAddMember( - '0x', - alice.address, - deployer.address - ) - ).to.not.be.reverted; + await expect(mainVotingPlugin.proposeAddMember('0x', alice.address)).to.not + .be.reverted; expect(await mainVotingPlugin.isMember(alice.address)).to.eq(true); }); From 43f9a3d90bb6f1718b1b53f19eb6fc90b8986285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:51:14 +0200 Subject: [PATCH 09/14] Adapting tests --- .../member-access-execute-condition.ts | 2 +- .../test/unit-testing/member-access-plugin.ts | 40 ++++--------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/packages/contracts/test/unit-testing/member-access-execute-condition.ts b/packages/contracts/test/unit-testing/member-access-execute-condition.ts index 3c4e325..2225df7 100644 --- a/packages/contracts/test/unit-testing/member-access-execute-condition.ts +++ b/packages/contracts/test/unit-testing/member-access-execute-condition.ts @@ -270,7 +270,7 @@ describe('Member Access Condition', function () { }); }); - describe('Direct add are not allowed', () => { + describe("Direct add's are not allowed", () => { it('Should reject adding and removing directly, rather than executing', async () => { // Valid expect( diff --git a/packages/contracts/test/unit-testing/member-access-plugin.ts b/packages/contracts/test/unit-testing/member-access-plugin.ts index 9a4a9e0..7b15ce5 100644 --- a/packages/contracts/test/unit-testing/member-access-plugin.ts +++ b/packages/contracts/test/unit-testing/member-access-plugin.ts @@ -50,8 +50,8 @@ export const defaultInitData: InitData = { export const multisigInterface = new ethers.utils.Interface([ 'function initialize(address,tuple(uint64,address))', - 'function updateMultisigSettings(tuple(uint64,address))', - 'function proposeNewMember(bytes,address)', + 'function updateMultisigSettings(tuple(uint64))', + 'function proposeAddMember(bytes,address,address)', 'function getProposal(uint256)', ]); const mainVotingPluginInterface = MainVotingPlugin__factory.createInterface(); @@ -763,7 +763,7 @@ describe('Member Access Plugin', function () { .connect(dave) .proposeAddMember(toUtf8Bytes('ipfs://1234'), alice.address) ) - .to.be.revertedWithCustomError(memberAccessPlugin, 'AlreadyMember') + .to.be.revertedWithCustomError(mainVotingPlugin, 'AlreadyAMember') .withArgs(alice.address); await expect( @@ -771,7 +771,7 @@ describe('Member Access Plugin', function () { .connect(dave) .proposeAddMember(toUtf8Bytes('ipfs://1234'), bob.address) ) - .to.be.revertedWithCustomError(memberAccessPlugin, 'AlreadyMember') + .to.be.revertedWithCustomError(mainVotingPlugin, 'AlreadyAMember') .withArgs(bob.address); }); @@ -843,32 +843,6 @@ describe('Member Access Plugin', function () { await expect(memberAccessPlugin.execute(pid)).to.be.reverted; }); - it('Fails to update the settings to use an incompatible main voting plugin', async () => { - const actionsWith = (days: number) => { - return [ - { - to: memberAccessPlugin.address, - value: 0, - data: MemberAccessPlugin__factory.createInterface().encodeFunctionData( - 'updateMultisigSettings', - [ - { - proposalDuration: 60 * 60 * 24 * days, - }, - ] - ), - }, - ] as IDAO.ActionStruct[]; - }; - - await expect(dao.execute(ZERO_BYTES32, actionsWith(1), 0)).to.be.reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(4), 0)).to.be.reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(10), 0)).to.be - .reverted; - await expect(dao.execute(ZERO_BYTES32, actionsWith(222), 0)).to.be - .reverted; - }); - it('Only the DAO can call the plugin to update the settings', async () => { // Nobody else can await expect( @@ -983,7 +957,7 @@ describe('Member Access Plugin', function () { memberAccessPlugin.initialize(dao.address, multisigSettings) ) .to.emit(memberAccessPlugin, 'MultisigSettingsUpdated') - .withArgs(60 * 60 * 24 * 5, mainVotingPlugin.address); + .withArgs(60 * 60 * 24 * 5); }); }); @@ -1045,7 +1019,7 @@ describe('Member Access Plugin', function () { memberAccessPlugin.updateMultisigSettings(multisigSettings) ) .to.emit(memberAccessPlugin, 'MultisigSettingsUpdated') - .withArgs(60 * 60 * 24 * 5, mainVotingPlugin.address); + .withArgs(60 * 60 * 24 * 5); }); }); @@ -1119,7 +1093,7 @@ describe('Member Access Plugin', function () { ) .to.revertedWithCustomError( memberAccessPlugin, - 'ProposalCreationForbidden' + 'ProposalCreationForbiddenOnSameBlock' ) .withArgs(alice.address); From 8049bab7951ff023d1ea226e8fd5791fd40f8a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:05:51 +0200 Subject: [PATCH 10/14] Adapting more tests --- .../src/test/TestGovernancePluginsSetup.sol | 18 ++++++------- .../src/test/TestMemberAccessPlugin.sol | 26 ------------------- 2 files changed, 9 insertions(+), 35 deletions(-) delete mode 100644 packages/contracts/src/test/TestMemberAccessPlugin.sol diff --git a/packages/contracts/src/test/TestGovernancePluginsSetup.sol b/packages/contracts/src/test/TestGovernancePluginsSetup.sol index d9faf1f..8734c6e 100644 --- a/packages/contracts/src/test/TestGovernancePluginsSetup.sol +++ b/packages/contracts/src/test/TestGovernancePluginsSetup.sol @@ -7,10 +7,10 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {PluginSetup, IPluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; -import {TestMemberAccessPlugin} from "./TestMemberAccessPlugin.sol"; import {MemberAccessExecuteCondition} from "../conditions/MemberAccessExecuteCondition.sol"; import {OnlyPluginUpgraderCondition} from "../conditions/OnlyPluginUpgraderCondition.sol"; import {MainVotingPlugin} from "../governance/MainVotingPlugin.sol"; +import {MemberAccessPlugin} from "../governance/MemberAccessPlugin.sol"; import {MajorityVotingBase} from "../governance/base/MajorityVotingBase.sol"; // Not ideal, but to test this E2E, the contract needs to be cloned @@ -27,7 +27,7 @@ contract TestGovernancePluginsSetup is PluginSetup { constructor(PluginSetupProcessor pluginSetupProcessorAddress) { pluginSetupProcessor = address(pluginSetupProcessorAddress); mainVotingPluginImplementationAddr = address(new MainVotingPlugin()); - memberAccessPluginImplementationAddr = address(new TestMemberAccessPlugin()); + memberAccessPluginImplementationAddr = address(new MemberAccessPlugin()); } /// @inheritdoc IPluginSetup @@ -45,12 +45,12 @@ contract TestGovernancePluginsSetup is PluginSetup { ) = decodeInstallationParams(_data); // Deploy the member access plugin - TestMemberAccessPlugin.MultisigSettings memory _multisigSettings; + MemberAccessPlugin.MultisigSettings memory _multisigSettings; _multisigSettings.proposalDuration = _memberAccessProposalDuration; address _memberAccessPlugin = createERC1967Proxy( memberAccessPluginImplementation(), - abi.encodeCall(TestMemberAccessPlugin.initialize, (IDAO(_dao), _multisigSettings)) + abi.encodeCall(MemberAccessPlugin.initialize, (IDAO(_dao), _multisigSettings)) ); // Deploy the main voting plugin @@ -62,7 +62,7 @@ contract TestGovernancePluginsSetup is PluginSetup { IDAO(_dao), _votingSettings, _initialEditors, - TestMemberAccessPlugin(_memberAccessPlugin) + MemberAccessPlugin(_memberAccessPlugin) ) ) ); @@ -111,7 +111,7 @@ contract TestGovernancePluginsSetup is PluginSetup { where: _memberAccessPlugin, who: mainVotingPlugin, condition: PermissionLib.NO_CONDITION, - permissionId: TestMemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + permissionId: MemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() }); // The member access plugin needs to execute on the DAO @@ -128,7 +128,7 @@ contract TestGovernancePluginsSetup is PluginSetup { where: _memberAccessPlugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: TestMemberAccessPlugin(_memberAccessPlugin) + permissionId: MemberAccessPlugin(_memberAccessPlugin) .UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() }); @@ -246,7 +246,7 @@ contract TestGovernancePluginsSetup is PluginSetup { where: _memberAccessPlugin, who: _payload.plugin, condition: PermissionLib.NO_CONDITION, - permissionId: TestMemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() + permissionId: MemberAccessPlugin(_memberAccessPlugin).PROPOSER_PERMISSION_ID() }); // The plugin can no longer execute on the DAO @@ -263,7 +263,7 @@ contract TestGovernancePluginsSetup is PluginSetup { where: _memberAccessPlugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: TestMemberAccessPlugin(memberAccessPluginImplementation()) + permissionId: MemberAccessPlugin(memberAccessPluginImplementation()) .UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() }); diff --git a/packages/contracts/src/test/TestMemberAccessPlugin.sol b/packages/contracts/src/test/TestMemberAccessPlugin.sol deleted file mode 100644 index 8b7c442..0000000 --- a/packages/contracts/src/test/TestMemberAccessPlugin.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {MemberAccessPlugin} from "../governance/MemberAccessPlugin.sol"; - -/// @notice A clone of the MemberAccessPlugin contract, just to test -contract TestMemberAccessPlugin is MemberAccessPlugin { - function initialize( - IDAO _dao, - MultisigSettings calldata _multisigSettings - ) public override initializer { - __PluginUUPSUpgradeable_init(_dao); - - _updateMultisigSettings(_multisigSettings); - } - - function devProposeAddMember( - bytes calldata _metadata, - address _proposedMember, - address _proposer - ) public returns (uint256 proposalId) { - return proposeAddMember(_metadata, _proposedMember, _proposer); - } -} From b8c632b5abc9194114e5ecda252445f9661c515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 7 Jun 2024 17:17:38 +0100 Subject: [PATCH 11/14] Adapted tests --- .../contracts/src/governance/MemberAccessPlugin.sol | 1 - .../test/unit-testing/member-access-plugin.ts | 12 +++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/contracts/src/governance/MemberAccessPlugin.sol b/packages/contracts/src/governance/MemberAccessPlugin.sol index db73b6e..a1777a7 100644 --- a/packages/contracts/src/governance/MemberAccessPlugin.sol +++ b/packages/contracts/src/governance/MemberAccessPlugin.sol @@ -71,7 +71,6 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade /// @notice A container for the plugin settings. /// @param proposalDuration The amount of time before a non-approved proposal expires. - /// @param mainVotingPlugin The address of the main voting plugin. Used to apply permissions for it. struct MultisigSettings { uint64 proposalDuration; } diff --git a/packages/contracts/test/unit-testing/member-access-plugin.ts b/packages/contracts/test/unit-testing/member-access-plugin.ts index 7b15ce5..aba26a9 100644 --- a/packages/contracts/test/unit-testing/member-access-plugin.ts +++ b/packages/contracts/test/unit-testing/member-access-plugin.ts @@ -49,7 +49,7 @@ export const defaultInitData: InitData = { }; export const multisigInterface = new ethers.utils.Interface([ - 'function initialize(address,tuple(uint64,address))', + 'function initialize(address,tuple(uint64))', 'function updateMultisigSettings(tuple(uint64))', 'function proposeAddMember(bytes,address,address)', 'function getProposal(uint256)', @@ -1090,12 +1090,10 @@ describe('Member Access Plugin', function () { ); await expect( mainVotingPlugin.proposeAddMember(EMPTY_DATA, carol.address) - ) - .to.revertedWithCustomError( - memberAccessPlugin, - 'ProposalCreationForbiddenOnSameBlock' - ) - .withArgs(alice.address); + ).to.revertedWithCustomError( + memberAccessPlugin, + 'ProposalCreationForbiddenOnSameBlock' + ); await ethers.provider.send('evm_setAutomine', [true]); }); From 8cd667a80b79252ef1029cecf310663329e6101a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 7 Jun 2024 17:26:40 +0100 Subject: [PATCH 12/14] Using node 18 --- .github/workflows/contracts-tests.yml | 2 +- .github/workflows/formatting-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index 3c4f725..e945230 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -25,7 +25,7 @@ jobs: uses: 'actions/setup-node@v3' with: cache: 'yarn' - node-version: 16 + node-version: 18 - name: 'Install the dependencies' run: 'yarn install' diff --git a/.github/workflows/formatting-check.yml b/.github/workflows/formatting-check.yml index 61b01b5..13d5cca 100644 --- a/.github/workflows/formatting-check.yml +++ b/.github/workflows/formatting-check.yml @@ -22,7 +22,7 @@ jobs: uses: 'actions/setup-node@v3' with: cache: 'yarn' - node-version: 16 + node-version: 18 - name: 'Install the dependencies' run: 'yarn install' From 9bf301640bcbcb67781db75c1ea35f2f40a450e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 7 Jun 2024 17:31:49 +0100 Subject: [PATCH 13/14] Updated dependencies --- packages/contracts/package.json | 2 +- yarn.lock | 113 ++++++++++++++------------------ 2 files changed, 51 insertions(+), 64 deletions(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 975b741..f7729e4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -32,7 +32,7 @@ "mocha": "^10.1.0", "solhint": "^3.4.0", "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.8.2", + "solidity-coverage": "^0.8.12", "tmp-promise": "^3.0.3" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 2649d35..6320650 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1209,7 +1209,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1": +"@solidity-parser/parser@^0.14.0": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg== @@ -1223,6 +1223,11 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.18.0.tgz#8e77a02a09ecce957255a2f48c9a7178ec191908" + integrity sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA== + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -1660,11 +1665,6 @@ acorn@^8.4.1, acorn@^8.8.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== -address@^1.0.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== - adm-zip@^0.4.16: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -2893,14 +2893,6 @@ detect-indent@^5.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== -detect-port@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== - dependencies: - address "^1.0.1" - debug "4" - diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -4051,6 +4043,17 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +glob@8.1.0, glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + glob@^10.2.5: version "10.2.7" resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.7.tgz#9dd2828cd5bc7bd861e7738d91e7113dda41d7d8" @@ -4085,17 +4088,6 @@ glob@^7.0.0, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -5442,36 +5434,6 @@ mnemonist@^0.38.0: dependencies: obliterator "^2.0.0" -mocha@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6" - integrity sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - mkdirp "0.5.5" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - mocha@^10.0.0, mocha@^10.1.0: version "10.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" @@ -5499,6 +5461,32 @@ mocha@^10.0.0, mocha@^10.1.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mocha@^10.2.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.4.0.tgz#ed03db96ee9cfc6d20c56f8e2af07b961dbae261" + integrity sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "8.1.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mocha@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -6932,24 +6920,23 @@ solidity-comments-extractor@^0.0.7: resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== -solidity-coverage@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" - integrity sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ== +solidity-coverage@^0.8.12: + version "0.8.12" + resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.12.tgz#c4fa2f64eff8ada7a1387b235d6b5b0e6c6985ed" + integrity sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw== dependencies: "@ethersproject/abi" "^5.0.9" - "@solidity-parser/parser" "^0.14.1" + "@solidity-parser/parser" "^0.18.0" chalk "^2.4.2" death "^1.1.0" - detect-port "^1.3.0" difflib "^0.2.4" fs-extra "^8.1.0" ghost-testrpc "^0.0.2" global-modules "^2.0.0" globby "^10.0.1" jsonschema "^1.2.4" - lodash "^4.17.15" - mocha "7.1.2" + lodash "^4.17.21" + mocha "^10.2.0" node-emoji "^1.10.0" pify "^4.0.1" recursive-readdir "^2.2.2" From 70e25ec3ac102436dd91e040626d2e2e72e472a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 7 Jun 2024 17:37:48 +0100 Subject: [PATCH 14/14] Remove unused imports --- .../test/integration-testing/member-access-condition.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/contracts/test/integration-testing/member-access-condition.ts b/packages/contracts/test/integration-testing/member-access-condition.ts index bbed7a0..3a3e390 100644 --- a/packages/contracts/test/integration-testing/member-access-condition.ts +++ b/packages/contracts/test/integration-testing/member-access-condition.ts @@ -4,8 +4,6 @@ import { MainVotingPlugin, MainVotingPlugin__factory, MajorityVotingBase, - MemberAccessPlugin, - MemberAccessPlugin__factory, PluginRepo, } from '../../typechain'; import {PluginSetupRefStruct} from '../../typechain/@aragon/osx/framework/dao/DAOFactory'; @@ -23,8 +21,6 @@ import { PluginSetupProcessor__factory, PluginRepoFactory__factory, PluginRepoRegistry__factory, - DAO__factory, - IDAO, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; @@ -38,14 +34,11 @@ const pluginSettings: MajorityVotingBase.VotingSettingsStruct = { votingMode: 0, }; const memberAccessProposalDuration = 60 * 60 * 24; -const daoInterface = DAO__factory.createInterface(); -const mainVotingInterface = MainVotingPlugin__factory.createInterface(); describe('Member Access Condition E2E', () => { let deployer: SignerWithAddress; let pluginUpgrader: SignerWithAddress; let alice: SignerWithAddress; - let bob: SignerWithAddress; let psp: PluginSetupProcessor; let dao: DAO; @@ -58,7 +51,7 @@ describe('Member Access Condition E2E', () => { // let memberAccessPlugin: MemberAccessPlugin; before(async () => { - [deployer, pluginUpgrader, alice, bob] = await ethers.getSigners(); + [deployer, pluginUpgrader, alice] = await ethers.getSigners(); // Get the PluginRepoFactory address const pluginRepoFactoryAddr: string = getPluginRepoFactoryAddress(