diff --git a/packages/contracts/src/governance/MainVotingPlugin.sol b/packages/contracts/src/governance/MainVotingPlugin.sol index 9ad4fc5..6aa37e9 100644 --- a/packages/contracts/src/governance/MainVotingPlugin.sol +++ b/packages/contracts/src/governance/MainVotingPlugin.sol @@ -62,6 +62,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers /// @notice Raised when a content proposal is called with empty data error EmptyContent(); + /// @notice Thrown when attempting propose removing membership for a non-member. + error AlreadyNotMember(address _member); + modifier onlyMembers() { if (!isMember(msg.sender)) { revert NotAMember(msg.sender); @@ -358,6 +361,62 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers }); } + /// @notice Creates a proposal to remove an existing member. + /// @param _metadata The metadata of the proposal. + /// @param _proposedMember The address of the member who may eveutnally be removed. + /// @param _spacePlugin The address of the space plugin where changes will be executed + function proposeRemoveMember( + bytes calldata _metadata, + address _proposedMember, + address _spacePlugin + ) public onlyMembers { + if (!isEditor(msg.sender)) { + revert ProposalCreationForbidden(msg.sender); + } else if (_spacePlugin == address(0)) { + revert EmptyContent(); + } else if (!isMember(_proposedMember)) { + revert AlreadyNotMember(_proposedMember); + } + 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. + } + uint64 _startDate = block.timestamp.toUint64(); + + uint256 proposalId = _createProposalId(); + + // Store proposal related information + Proposal storage proposal_ = proposals[proposalId]; + + proposal_.parameters.startDate = _startDate; + proposal_.parameters.endDate = _startDate + duration(); + proposal_.parameters.snapshotBlock = snapshotBlock; + proposal_.parameters.votingMode = votingMode(); + proposal_.parameters.supportThreshold = supportThreshold(); + proposal_.parameters.minVotingPower = _applyRatioCeiled( + totalVotingPower(snapshotBlock), + minParticipation() + ); + IDAO.Action memory _action = IDAO.Action({ + to: address(this), + value: 0, + data: abi.encodeCall(MainVotingPlugin.removeMember, (_proposedMember)) + }); + proposal_.actions.push(_action); + + proposalCreators[proposalId] = msg.sender; + + emit ProposalCreated({ + proposalId: proposalId, + creator: proposalCreators[proposalId], + metadata: _metadata, + startDate: _startDate, + endDate: proposal_.parameters.endDate, + actions: proposal_.actions, + allowFailureMap: 0 + }); + } + /// @inheritdoc MajorityVotingBase function _vote( uint256 _proposalId, diff --git a/packages/contracts/src/governance/MemberAccessPlugin.sol b/packages/contracts/src/governance/MemberAccessPlugin.sol index 2b9006a..12e5cf2 100644 --- a/packages/contracts/src/governance/MemberAccessPlugin.sol +++ b/packages/contracts/src/governance/MemberAccessPlugin.sol @@ -14,7 +14,6 @@ import {MainVotingPlugin, MAIN_SPACE_VOTING_INTERFACE_ID} from "./MainVotingPlug bytes4 constant MEMBER_ACCESS_INTERFACE_ID = MemberAccessPlugin.initialize.selector ^ MemberAccessPlugin.updateMultisigSettings.selector ^ MemberAccessPlugin.proposeNewMember.selector ^ - MemberAccessPlugin.proposeRemoveMember.selector ^ MemberAccessPlugin.getProposal.selector; /// @title Member access plugin (Multisig) - Release 1, Build 1 @@ -281,32 +280,6 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade _executeProposal(dao(), _createProposalId(), proposals[_proposalId].actions, 0); } - /// @notice Creates a proposal to remove an existing member. - /// @param _metadata The metadata of the proposal. - /// @param _proposedMember The address of the member who may eveutnally be removed. - /// @return proposalId The ID of the proposal. - function proposeRemoveMember( - bytes calldata _metadata, - address _proposedMember - ) external returns (uint256 proposalId) { - if (!isEditor(msg.sender)) { - revert ProposalCreationForbidden(msg.sender); - } else if (!isMember(_proposedMember)) { - revert AlreadyNotMember(_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.removeMember, (_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 {