diff --git a/src/Space.sol b/src/Space.sol index ea4b748b..ed2a8cd4 100644 --- a/src/Space.sol +++ b/src/Space.sol @@ -6,8 +6,8 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import { IERC4824 } from "src/interfaces/IERC4824.sol"; -import { ISpace, ISpaceActions, ISpaceState, ISpaceOwnerActions } from "src/interfaces/ISpace.sol"; +import { IERC4824 } from "./interfaces/IERC4824.sol"; +import { ISpace, ISpaceActions, ISpaceState, ISpaceOwnerActions } from "./interfaces/ISpace.sol"; import { Choice, FinalizationStatus, @@ -19,10 +19,10 @@ import { InitializeCalldata, TRUE, FALSE -} from "src/types.sol"; -import { IVotingStrategy } from "src/interfaces/IVotingStrategy.sol"; -import { IExecutionStrategy } from "src/interfaces/IExecutionStrategy.sol"; -import { IProposalValidationStrategy } from "src/interfaces/IProposalValidationStrategy.sol"; +} from "./types.sol"; +import { IVotingStrategy } from "./interfaces/IVotingStrategy.sol"; +import { IExecutionStrategy } from "./interfaces/IExecutionStrategy.sol"; +import { IProposalValidationStrategy } from "./interfaces/IProposalValidationStrategy.sol"; import { SXUtils } from "./utils/SXUtils.sol"; import { BitPacker } from "./utils/BitPacker.sol"; @@ -65,8 +65,9 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra Strategy public override proposalValidationStrategy; /// @inheritdoc ISpaceState mapping(address auth => uint256 allowed) public override authenticators; - /// @inheritdoc ISpaceState - mapping(uint256 proposalId => Proposal proposal) public override proposals; + + mapping(uint256 proposalId => Proposal proposal) public proposals; + // @inheritdoc ISpaceState mapping(uint256 proposalId => mapping(Choice choice => uint256 votePower)) public override votePower; /// @inheritdoc ISpaceState @@ -168,6 +169,15 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra } } + function setAuthenticator(address auth) external onlyOwner{ + authenticators[auth] = TRUE; + } + + function addVotingStrategy(Strategy[] calldata newStrategy) external onlyAuthenticator returns(uint8){ + _addVotingStrategies(newStrategy); + return nextVotingStrategyIndex - 1; + } + /// @dev Gates access to whitelisted authenticators only. modifier onlyAuthenticator() { if (authenticators[msg.sender] == FALSE) revert AuthenticatorNotWhitelisted(); @@ -204,7 +214,8 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra address author, string calldata metadataURI, Strategy calldata executionStrategy, - bytes calldata userProposalValidationParams + bytes calldata userProposalValidationParams, + uint8[] calldata selectedVotingStrategiesIndices ) external override onlyAuthenticator { if ( !IProposalValidationStrategy(proposalValidationStrategy.addr).validate( @@ -230,9 +241,10 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra maxEndBlockNumber, FinalizationStatus.Pending, executionPayloadHash, - activeVotingStrategies + activeVotingStrategies, + _getSelectedBitArray(selectedVotingStrategiesIndices) ); - + proposals[nextProposalId] = proposal; emit ProposalCreated(nextProposalId, author, proposal, metadataURI, executionStrategy.params); @@ -256,11 +268,16 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra voteRegistry[proposalId][voter] = TRUE; + uint256 activeSelectedVotingStrategies = + proposal.selectedVotingStrategies > 0 ? + proposal.selectedVotingStrategies : + proposal.activeVotingStrategies; + uint256 votingPower = _getCumulativePower( voter, proposal.startBlockNumber, userVotingStrategies, - proposal.activeVotingStrategies + activeSelectedVotingStrategies ); if (votingPower == 0) revert UserHasNoVotingPower(); votePower[proposalId][choice] += votingPower; @@ -435,4 +452,17 @@ contract Space is ISpace, Initializable, IERC4824, UUPSUpgradeable, OwnableUpgra } return totalVotingPower; } + + function _getSelectedBitArray(uint8[] calldata selectedIndices) internal returns(uint256) { + uint256 cachedActiveStrategies = activeVotingStrategies; + uint256 selectedVotingStrategies; + for(uint256 i = 0; i < selectedIndices.length; i++) { + uint8 strategyIndex = selectedIndices[i]; + // Selected strategy must also be active + if(cachedActiveStrategies.isBitSet(strategyIndex)){ + selectedVotingStrategies = selectedVotingStrategies.setBit(strategyIndex, true); + } + } + return selectedVotingStrategies; + } } diff --git a/src/interfaces/IERC4824.sol b/src/interfaces/IERC4824.sol index 60977529..6d23fd7a 100644 --- a/src/interfaces/IERC4824.sol +++ b/src/interfaces/IERC4824.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.18; +pragma solidity 0.8.19; /// @title EIP-4824 Common Interfaces for DAOs /// @notice See https://eips.ethereum.org/EIPS/eip-4824 diff --git a/src/interfaces/space/ISpaceActions.sol b/src/interfaces/space/ISpaceActions.sol index cd66a5f1..227a5fed 100644 --- a/src/interfaces/space/ISpaceActions.sol +++ b/src/interfaces/space/ISpaceActions.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.18; -import { Choice, IndexedStrategy, Strategy, InitializeCalldata } from "src/types.sol"; +import { Choice, IndexedStrategy, Strategy, InitializeCalldata } from "../../types.sol"; /// @title Space Actions /// @notice User focused actions that can be performed on a space. @@ -31,11 +31,13 @@ interface ISpaceActions { /// @param executionStrategy The execution strategy for the proposal, /// consisting of a strategy address and an execution payload. /// @param userProposalValidationParams The user provided parameters for proposal validation. + /// @param selectedVotingStrategyIndices The indices of voting strategies to use for this specific proposal. Empty array yields all active. function propose( address author, string calldata metadataURI, Strategy calldata executionStrategy, - bytes calldata userProposalValidationParams + bytes calldata userProposalValidationParams, + uint8[] calldata selectedVotingStrategyIndices ) external; /// @notice Casts a vote. diff --git a/src/interfaces/space/ISpaceEvents.sol b/src/interfaces/space/ISpaceEvents.sol index a22f922f..53794ae9 100644 --- a/src/interfaces/space/ISpaceEvents.sol +++ b/src/interfaces/space/ISpaceEvents.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import { IndexedStrategy, Proposal, Strategy, Choice, InitializeCalldata } from "src/types.sol"; +import { IndexedStrategy, Proposal, Strategy, Choice, InitializeCalldata } from "../../types.sol"; /// @title Space Events interface ISpaceEvents { diff --git a/src/interfaces/space/ISpaceState.sol b/src/interfaces/space/ISpaceState.sol index 8841bd2e..0e01aea6 100644 --- a/src/interfaces/space/ISpaceState.sol +++ b/src/interfaces/space/ISpaceState.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.18; -import { Choice, Proposal, ProposalStatus, FinalizationStatus, Strategy } from "src/types.sol"; -import { IExecutionStrategy } from "src/interfaces/IExecutionStrategy.sol"; +import { Choice, Proposal, ProposalStatus, FinalizationStatus, Strategy } from "../../types.sol"; +import { IExecutionStrategy } from "../../interfaces/IExecutionStrategy.sol"; /// @title Space State interface ISpaceState { @@ -63,6 +63,7 @@ interface ISpaceState { /// @return finalizationStatus The finalization status of the proposal. See `FinalizationStatus`. /// @return executionPayloadHash The keccak256 hash of the execution payload. /// @return activeVotingStrategies The bit array of the active voting strategies for the proposal. + /// @return selectedVotingStrategies function proposals( uint256 proposalId ) @@ -76,7 +77,8 @@ interface ISpaceState { uint32 maxEndBlockNumber, FinalizationStatus finalizationStatus, bytes32 executionPayloadHash, - uint256 activeVotingStrategies + uint256 activeVotingStrategies, + uint256 selectedVotingStrategies ); /// @notice Returns the status of a proposal. diff --git a/src/types.sol b/src/types.sol index bb0ccfc8..abeee9e8 100644 --- a/src/types.sol +++ b/src/types.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; import { Enum } from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; -import { IExecutionStrategy } from "src/interfaces/IExecutionStrategy.sol"; +import { IExecutionStrategy } from "./interfaces/IExecutionStrategy.sol"; /// @dev Constants used to replace the `bool` type in mappings for gas efficiency. uint256 constant TRUE = 1; @@ -37,6 +37,10 @@ struct Proposal { // Bit array where the index of each each bit corresponds to whether the voting strategy. // at that index is active at the time of proposal creation. uint256 activeVotingStrategies; + // SLOT 5: + // Bit array where the index of each bit corresponds to whether the voting strategy + // at that index was selected for the given proposal. empty array/0 indicates all active strategies are valid. + uint256 selectedVotingStrategies; } /// @notice The data stored for each strategy. diff --git a/src/utils/SXUtils.sol b/src/utils/SXUtils.sol index e31d2e03..2e51b87f 100644 --- a/src/utils/SXUtils.sol +++ b/src/utils/SXUtils.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.18; -import { IndexedStrategy } from "src/types.sol"; +import { IndexedStrategy } from "../types.sol"; /// @title Snapshot X Types Utilities Library library SXUtils {