diff --git a/src/LockToApprovePlugin.sol b/src/LockToApprovePlugin.sol index f32dd8f..4f9130e 100644 --- a/src/LockToApprovePlugin.sol +++ b/src/LockToApprovePlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.13; -import {ILockManager} from "./interfaces/ILockManager.sol"; +import {ILockManager, UnlockMode} from "./interfaces/ILockManager.sol"; import {ILockToVoteBase} from "./interfaces/ILockToVote.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {LockToVoteBase} from "./base/LockToVoteBase.sol"; @@ -169,6 +169,8 @@ contract LockToApprovePlugin is revert NoVotingPower(); } + /// @dev `minProposerVotingPower` will be checked by the permission condition behind auth(CREATE_PROPOSAL_PERMISSION_ID) + (_startDate, _endDate) = _validateProposalDates(_startDate, _endDate); proposalId = _createProposalId(keccak256(abi.encode(_actions, _metadata))); @@ -247,25 +249,35 @@ contract LockToApprovePlugin is } /// @inheritdoc ILockToApprove - function approve(uint256 _proposalId, address _voter, uint256 _newVotingPower) - external - auth(LOCK_MANAGER_PERMISSION_ID) - { - ProposalApproval storage proposal_ = proposals[_proposalId]; + function approve( + uint256 _proposalId, + address _voter, + uint256 _currentVotingPower + ) external auth(LOCK_MANAGER_PERMISSION_ID) { + Proposal storage proposal_ = proposals[_proposalId]; - if (!_canVote(proposal_, _voter, _newVotingPower)) { + if (!_canApprove(proposal_, _voter, _currentVotingPower)) { revert ApprovalForbidden(_proposalId, _voter); } // Add the difference between the new voting power and the current one - uint256 diff = _newVotingPower - proposal_.approvals[_voter]; + uint256 diff = _currentVotingPower - proposal_.approvals[_voter]; proposal_.approvalTally += diff; proposal_.approvals[_voter] += diff; - emit Approved(_proposalId, _voter, _newVotingPower); + emit ApprovalCast(_proposalId, _voter, _currentVotingPower); - _checkEarlyExecution(_proposalId, proposal_, _voter); + // Check if we may execute early + (UnlockMode unlockMode, ) = lockManager.settings(); + if (unlockMode == UnlockMode.STRICT) { + if ( + _canExecute(proposal_) && + dao().hasPermission(address(this), _msgSender(), EXECUTE_PROPOSAL_PERMISSION_ID, _msgData()) + ) { + _execute(_proposalId, proposal_); + } + } } /// @inheritdoc ILockToApprove @@ -289,47 +301,40 @@ contract LockToApprovePlugin is } /// @inheritdoc IProposal - function hasSucceeded(uint256 _proposalId) external view returns (bool) { + function canExecute(uint256 _proposalId) external view returns (bool) { if (!_proposalExists(_proposalId)) { revert NonexistentProposal(_proposalId); } Proposal storage proposal_ = proposals[_proposalId]; - return _hasSucceeded(proposal_); + return _canExecute(proposal_); } /// @inheritdoc IProposal - function canExecute(uint256 _proposalId) external view returns (bool) { - ProposalApproval storage proposal_ = proposals[_proposalId]; - return _canExecute(proposal_); + function hasSucceeded(uint256 _proposalId) external view returns (bool) { + if (!_proposalExists(_proposalId)) { + revert NonexistentProposal(_proposalId); + } + + Proposal storage proposal_ = proposals[_proposalId]; + return _hasSucceeded(proposal_); } /// @inheritdoc IProposal function execute(uint256 _proposalId) external auth(EXECUTE_PROPOSAL_PERMISSION_ID) { - ProposalApproval storage proposal_ = proposals[_proposalId]; + Proposal storage proposal_ = proposals[_proposalId]; if (!_canExecute(proposal_)) { - revert ExecutionForbidden(_proposalId); + revert ProposalExecutionForbidden(_proposalId); } _execute(_proposalId, proposal_); } - /// @inheritdoc ILockToVoteBase - function underlyingToken() external view returns (IERC20) { - return lockManager.underlyingToken(); - } - - /// @inheritdoc ILockToVoteBase - function token() external view returns (IERC20) { - return lockManager.token(); - } - /// @inheritdoc ILockToApprove - function updatePluginSettings(LockToApproveSettings calldata _newSettings) - external - auth(UPDATE_VOTING_SETTINGS_PERMISSION_ID) - { + function updatePluginSettings( + ApprovalSettings calldata _newSettings + ) external auth(UPDATE_VOTING_SETTINGS_PERMISSION_ID) { _updatePluginSettings(_newSettings); } @@ -436,28 +441,28 @@ contract LockToApprovePlugin is } } - function _checkEarlyExecution(uint256 _proposalId, ProposalApproval storage proposal_, address _voter) internal { - if (!_canExecute(proposal_)) { - return; - } else if (!dao().hasPermission(address(this), _voter, EXECUTE_PROPOSAL_PERMISSION_ID, _msgData())) { - return; - } - - _execute(_proposalId, proposal_); - } - - function _execute(uint256 _proposalId, ProposalApproval storage proposal_) internal { + function _execute(uint256 _proposalId, Proposal storage proposal_) internal { proposal_.executed = true; // IProposal's target execution - _execute(bytes32(_proposalId), proposal_.actions, proposal_.allowFailureMap); + _execute( + proposal_.targetConfig.target, + bytes32(_proposalId), + proposal_.actions, + proposal_.allowFailureMap, + proposal_.targetConfig.operation + ); - emit Executed(_proposalId); + emit ProposalExecuted(_proposalId); // Notify the LockManager to stop tracking this proposal ID lockManager.proposalEnded(_proposalId); } + function _updatePluginSettings(ApprovalSettings memory _newSettings) internal { + settings.minApprovalRatio = _newSettings.minApprovalRatio; + settings.minProposalDuration = _newSettings.minProposalDuration; + } /// @notice This empty reserved space is put in place to allow future versions to add /// new variables without shifting down storage in the inheritance chain diff --git a/src/LockToVotePlugin.sol b/src/LockToVotePlugin.sol index 9f2959e..c8033f4 100644 --- a/src/LockToVotePlugin.sol +++ b/src/LockToVotePlugin.sol @@ -112,6 +112,8 @@ contract LockToVotePlugin is ILockToVote, MajorityVotingBase, LockToVoteBase { revert NoVotingPower(); } + /// @dev `minProposerVotingPower` will be checked by the permission condition behind auth(CREATE_PROPOSAL_PERMISSION_ID) + (_startDate, _endDate) = _validateProposalDates(_startDate, _endDate); proposalId = _createProposalId( @@ -185,22 +187,23 @@ contract LockToVotePlugin is ILockToVote, MajorityVotingBase, LockToVoteBase { uint256 _proposalId, address _voter, VoteOption _voteOption, - uint256 _votingPower + uint256 _currentVotingPower ) public override auth(LOCK_MANAGER_PERMISSION_ID) { Proposal storage proposal_ = proposals[_proposalId]; - if (!_canVote(proposal_, _voter, _voteOption, _votingPower)) { + if (!_canVote(proposal_, _voter, _voteOption, _currentVotingPower)) { revert VoteCastForbidden(_proposalId, _voter); } // Same vote if (_voteOption == proposal_.votes[_voter].voteOption) { // Same balance, nothing to do - if (_votingPower == proposal_.votes[_voter].votingPower) return; + if (_currentVotingPower == proposal_.votes[_voter].votingPower) return; // More balance - uint256 diff = _votingPower - proposal_.votes[_voter].votingPower; - proposal_.votes[_voter].votingPower = _votingPower; + /// @dev Positive diff is guaranteed, as _canVote() above will return false and revert otherwise + uint256 diff = _currentVotingPower - proposal_.votes[_voter].votingPower; + proposal_.votes[_voter].votingPower = _currentVotingPower; if (proposal_.votes[_voter].voteOption == VoteOption.Yes) { proposal_.tally.yes += diff; @@ -230,17 +233,17 @@ contract LockToVotePlugin is ILockToVote, MajorityVotingBase, LockToVoteBase { // Register the new vote if (_voteOption == VoteOption.Yes) { - proposal_.tally.yes += _votingPower; + proposal_.tally.yes += _currentVotingPower; } else if (_voteOption == VoteOption.No) { - proposal_.tally.no += _votingPower; + proposal_.tally.no += _currentVotingPower; } else { - proposal_.tally.abstain += _votingPower; + proposal_.tally.abstain += _currentVotingPower; } proposal_.votes[_voter].voteOption = _voteOption; - proposal_.votes[_voter].votingPower = _votingPower; + proposal_.votes[_voter].votingPower = _currentVotingPower; } - emit VoteCast(_proposalId, _voter, _voteOption, _votingPower); + emit VoteCast(_proposalId, _voter, _voteOption, _currentVotingPower); if (proposal_.parameters.votingMode == VotingMode.EarlyExecution) { _checkEarlyExecution(_proposalId, _msgSender()); diff --git a/src/interfaces/ILockManager.sol b/src/interfaces/ILockManager.sol index 48679a8..83aaea7 100644 --- a/src/interfaces/ILockManager.sol +++ b/src/interfaces/ILockManager.sol @@ -30,6 +30,9 @@ struct LockManagerSettings { /// @author Aragon X 2024 /// @notice Helper contract acting as the vault for locked tokens used to vote on multiple plugins and proposals. interface ILockManager { + /// @notice Returns the current settings of the LockManager. + function settings() external view returns (UnlockMode unlockMode, PluginMode pluginMode); + /// @notice Returns the address of the voting plugin. /// @return The LockToVote plugin address. function plugin() external view returns (ILockToVoteBase); @@ -54,10 +57,7 @@ interface ILockManager { /// @notice Locks the balance currently allowed by msg.sender on this contract and registers the given vote on the target plugin /// @param proposalId The ID of the proposal where the vote will be registered - function lockAndVote( - uint256 proposalId, - IMajorityVoting.VoteOption vote - ) external; + function lockAndVote(uint256 proposalId, IMajorityVoting.VoteOption vote) external; /// @notice Uses the locked balance to place an approval on the given proposal for the registered plugin /// @param proposalId The ID of the proposal where the approval will be registered