pragma solidity ^0.4.25; import "./DaoCommonMini.sol"; import "../lib/MathHelper.sol"; contract DaoCommon is DaoCommonMini { using MathHelper for MathHelper; /** @notice Check if a proposal is currently paused/stopped @dev If a proposal is paused/stopped (by the PRLs): proposer cannot call for voting, a current on-going voting round can still pass, but no funding can be withdrawn. @dev A paused proposal can still be unpaused @dev If a proposal is stopped, this function also returns true @return _isPausedOrStopped true if the proposal is paused(or stopped) */ function isProposalPaused(bytes32 _proposalId) public view returns (bool _isPausedOrStopped) { (,,,,,,,,_isPausedOrStopped,) = daoStorage().readProposal(_proposalId); } /** @notice Check if the transaction is called by the proposer of a proposal @return _isFromProposer true if the caller is the proposer */ function isFromProposer(bytes32 _proposalId) internal view returns (bool _isFromProposer) { _isFromProposer = msg.sender == daoStorage().readProposalProposer(_proposalId); } /** @notice Check if the proposal can still be "editted", or in other words, added more versions @dev Once the proposal is finalized, it can no longer be editted. The proposer will still be able to add docs and change fundings though. @return _isEditable true if the proposal is editable */ function isEditable(bytes32 _proposalId) internal view returns (bool _isEditable) { bytes32 _finalVersion; (,,,,,,,_finalVersion,,) = daoStorage().readProposal(_proposalId); _isEditable = _finalVersion == EMPTY_BYTES; } /** @notice returns the balance of DaoFundingManager, which is the wei in DigixDAO */ function weiInDao() internal view returns (uint256 _wei) { _wei = get_contract(CONTRACT_DAO_FUNDING_MANAGER).balance; } /** @notice Check if it is after the draft voting phase of the proposal */ modifier ifAfterDraftVotingPhase(bytes32 _proposalId) { uint256 _start = daoStorage().readProposalDraftVotingTime(_proposalId); require(_start > 0); // Draft voting must have started. In other words, proposer must have finalized the proposal require(now >= _start.add(getUintConfig(CONFIG_DRAFT_VOTING_PHASE))); _; } modifier ifCommitPhase(bytes32 _proposalId, uint8 _index) { requireInPhase( daoStorage().readProposalVotingTime(_proposalId, _index), 0, getUintConfig(_index == 0 ? CONFIG_VOTING_COMMIT_PHASE : CONFIG_INTERIM_COMMIT_PHASE) ); _; } modifier ifRevealPhase(bytes32 _proposalId, uint256 _index) { requireInPhase( daoStorage().readProposalVotingTime(_proposalId, _index), getUintConfig(_index == 0 ? CONFIG_VOTING_COMMIT_PHASE : CONFIG_INTERIM_COMMIT_PHASE), getUintConfig(_index == 0 ? CONFIG_VOTING_PHASE_TOTAL : CONFIG_INTERIM_PHASE_TOTAL) ); _; } modifier ifAfterProposalRevealPhase(bytes32 _proposalId, uint256 _index) { uint256 _start = daoStorage().readProposalVotingTime(_proposalId, _index); require(_start > 0); require(now >= _start.add(getUintConfig(_index == 0 ? CONFIG_VOTING_PHASE_TOTAL : CONFIG_INTERIM_PHASE_TOTAL))); _; } modifier ifDraftVotingPhase(bytes32 _proposalId) { requireInPhase( daoStorage().readProposalDraftVotingTime(_proposalId), 0, getUintConfig(CONFIG_DRAFT_VOTING_PHASE) ); _; } modifier isProposalState(bytes32 _proposalId, bytes32 _STATE) { bytes32 _currentState; (,,,_currentState,,,,,,) = daoStorage().readProposal(_proposalId); require(_currentState == _STATE); _; } /** @notice Check if the DAO has enough ETHs for a particular funding request */ modifier ifFundingPossible(uint256[] _fundings, uint256 _finalReward) { require(MathHelper.sumNumbers(_fundings).add(_finalReward) <= weiInDao()); _; } modifier ifDraftNotClaimed(bytes32 _proposalId) { require(daoStorage().isDraftClaimed(_proposalId) == false, "draft is already claimed"); _; } modifier ifNotClaimed(bytes32 _proposalId, uint256 _index) { require(daoStorage().isClaimed(_proposalId, _index) == false, "voting round already claimed"); _; } modifier ifNotClaimedSpecial(bytes32 _proposalId) { require(daoSpecialStorage().isClaimed(_proposalId) == false); _; } modifier hasNotRevealed(bytes32 _proposalId, uint256 _index) { uint256 _voteWeight; (, _voteWeight) = daoStorage().readVote(_proposalId, _index, msg.sender); require(_voteWeight == uint(0), "has already revealed"); _; } modifier hasNotRevealedSpecial(bytes32 _proposalId) { uint256 _weight; (,_weight) = daoSpecialStorage().readVote(_proposalId, msg.sender); require(_weight == uint256(0)); _; } modifier ifAfterRevealPhaseSpecial(bytes32 _proposalId) { uint256 _start = daoSpecialStorage().readVotingTime(_proposalId); require(_start > 0); require(now.sub(_start) >= getUintConfig(CONFIG_SPECIAL_PROPOSAL_PHASE_TOTAL)); _; } modifier ifCommitPhaseSpecial(bytes32 _proposalId) { requireInPhase( daoSpecialStorage().readVotingTime(_proposalId), 0, getUintConfig(CONFIG_SPECIAL_PROPOSAL_COMMIT_PHASE) ); _; } modifier ifRevealPhaseSpecial(bytes32 _proposalId) { requireInPhase( daoSpecialStorage().readVotingTime(_proposalId), getUintConfig(CONFIG_SPECIAL_PROPOSAL_COMMIT_PHASE), getUintConfig(CONFIG_SPECIAL_PROPOSAL_PHASE_TOTAL) ); _; } function daoWhitelistingStorage() internal view returns (DaoWhitelistingStorage _contract) { _contract = DaoWhitelistingStorage(get_contract(CONTRACT_STORAGE_DAO_WHITELISTING)); } function getAddressConfig(bytes32 _configKey) public view returns (address _configValue) { _configValue = daoConfigsStorage().addressConfigs(_configKey); } function getBytesConfig(bytes32 _configKey) public view returns (bytes32 _configValue) { _configValue = daoConfigsStorage().bytesConfigs(_configKey); } /** @notice Check if a user is a participant in the current quarter */ function isParticipant(address _user) public view returns (bool _is) { _is = (daoRewardsStorage().lastParticipatedQuarter(_user) == currentQuarterNumber()) && (daoStakeStorage().lockedDGDStake(_user) >= getUintConfig(CONFIG_MINIMUM_LOCKED_DGD)); } /** @notice Check if a user is a moderator in the current quarter */ function isModerator(address _user) public view returns (bool _is) { _is = (daoRewardsStorage().lastParticipatedQuarter(_user) == currentQuarterNumber()) && (daoStakeStorage().lockedDGDStake(_user) >= getUintConfig(CONFIG_MINIMUM_DGD_FOR_MODERATOR)) && (daoPointsStorage().getReputation(_user) >= getUintConfig(CONFIG_MINIMUM_REPUTATION_FOR_MODERATOR)); } /** @notice Calculate the start of a specific milestone of a specific proposal. @dev This is calculated from the voting start of the voting round preceding the milestone This would throw if the voting start is 0 (the voting round has not started yet) Note that if the milestoneIndex is exactly the same as the number of milestones, This will just return the end of the last voting round. */ function startOfMilestone(bytes32 _proposalId, uint256 _milestoneIndex) internal view returns (uint256 _milestoneStart) { uint256 _startOfPrecedingVotingRound = daoStorage().readProposalVotingTime(_proposalId, _milestoneIndex); require(_startOfPrecedingVotingRound > 0); // the preceding voting round must have started if (_milestoneIndex == 0) { // This is the 1st milestone, which starts after voting round 0 _milestoneStart = _startOfPrecedingVotingRound .add(getUintConfig(CONFIG_VOTING_PHASE_TOTAL)); } else { // if its the n-th milestone, it starts after voting round n-th _milestoneStart = _startOfPrecedingVotingRound .add(getUintConfig(CONFIG_INTERIM_PHASE_TOTAL)); } } /** @notice Calculate the actual voting start for a voting round, given the tentative start @dev The tentative start is the ideal start. For example, when a proposer finish a milestone, it should be now However, sometimes the tentative start is too close to the end of the quarter, hence, the actual voting start should be pushed to the next quarter */ function getTimelineForNextVote( uint256 _index, uint256 _tentativeVotingStart ) internal view returns (uint256 _actualVotingStart) { uint256 _timeLeftInQuarter = getTimeLeftInQuarter(_tentativeVotingStart); uint256 _votingDuration = getUintConfig(_index == 0 ? CONFIG_VOTING_PHASE_TOTAL : CONFIG_INTERIM_PHASE_TOTAL); _actualVotingStart = _tentativeVotingStart; if (timeInQuarter(_tentativeVotingStart) < getUintConfig(CONFIG_LOCKING_PHASE_DURATION)) { // if the tentative start is during a locking phase _actualVotingStart = _tentativeVotingStart.add( getUintConfig(CONFIG_LOCKING_PHASE_DURATION).sub(timeInQuarter(_tentativeVotingStart)) ); } else if (_timeLeftInQuarter < _votingDuration.add(getUintConfig(CONFIG_VOTE_CLAIMING_DEADLINE))) { // if the time left in quarter is not enough to vote and claim voting _actualVotingStart = _tentativeVotingStart.add( _timeLeftInQuarter.add(getUintConfig(CONFIG_LOCKING_PHASE_DURATION)).add(1) ); } } /** @notice Check if we can add another non-Digix proposal in this quarter @dev There is a max cap to the number of non-Digix proposals CONFIG_NON_DIGIX_PROPOSAL_CAP_PER_QUARTER */ function checkNonDigixProposalLimit(bytes32 _proposalId) internal view { require(isNonDigixProposalsWithinLimit(_proposalId), "not within funding limits"); } function isNonDigixProposalsWithinLimit(bytes32 _proposalId) internal view returns (bool _withinLimit) { bool _isDigixProposal; (,,,,,,,,,_isDigixProposal) = daoStorage().readProposal(_proposalId); _withinLimit = true; if (!_isDigixProposal) { _withinLimit = daoProposalCounterStorage().proposalCountByQuarter(currentQuarterNumber()) < getUintConfig(CONFIG_NON_DIGIX_PROPOSAL_CAP_PER_QUARTER); } } /** @notice If its a non-Digix proposal, check if the fundings are within limit @dev There is a max cap to the fundings and number of milestones for non-Digix proposals */ function checkNonDigixFundings(uint256[] _milestonesFundings, uint256 _finalReward) internal view { if (!is_founder()) { require(_milestonesFundings.length <= getUintConfig(CONFIG_MAX_MILESTONES_FOR_NON_DIGIX)); require(MathHelper.sumNumbers(_milestonesFundings).add(_finalReward) <= getUintConfig(CONFIG_MAX_FUNDING_FOR_NON_DIGIX)); } } /** @notice Check if msg.sender can do operations as a proposer @dev Note that this function does not check if he is the proposer of the proposal */ function senderCanDoProposerOperations() internal view { require(isMainPhase(), "not main phase"); require(isParticipant(msg.sender), "not participant"); require(identity_storage().is_kyc_approved(msg.sender), "not kyc approved"); } }