CompleteDAOGovernance

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/**
 * @title CompleteDAOGovernance
 * @dev Complete DAO governance system with all features integrated
 * @notice Users with 0.25%+ of total supply can create proposals
 * @notice Token holders and stakers can vote on proposals
 */

// ============ INTERFACES ============

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IStakingContract {
    function Stakers(address user) external view returns (
        uint256 totalStaked,
        uint256 totalUnStaked,
        uint256 totalClaimedReward,
        uint256 stakeCount,
        bool alreadyExists
    );
}

// ============ LIBRARIES ============

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        return c;
    }
}

// ============ MAIN CONTRACT ============

contract CompleteDAOGovernance {
    using SafeMath for uint256;

    // ============ STATE VARIABLES ============
    
    IERC20 public immutable governanceToken;
    IStakingContract public immutable stakingContract;
    
    // Production settings
    uint256 public proposalThresholdPercent = 25; // 0.25% for production
    uint256 public constant BASIS_POINTS = 10000;
    uint256 public constant VOTING_DELAY = 1 days;            // 1 day delay for production
    uint256 public constant VOTING_PERIOD = 7 days;          // 7 days for production
    // Mutable quorum in basis points (parts per 10,000). Default 10% for production.
    uint256 public quorumPercentBps = 1000;
    uint256 public constant EMERGENCY_VOTING_PERIOD = 2 days;
    
    uint256 public proposalCount;
    uint256 public totalSupply;
    bool public emergencyMode = false;
    
    // ============ STRUCTS ============
    
    struct Proposal {
        uint256 id;
        address proposer;
        uint256 startTimestamp;
        uint256 endTimestamp;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 abstainVotes;
        bool canceled;
        bool emergency;
        string title;
    }
    
    struct Receipt {
        bool hasVoted;
        uint8 support; // 0 = Against, 1 = For, 2 = Abstain
        uint256 votes;
    }
    
    // ============ MAPPINGS ============
    
    mapping(uint256 => Proposal) public proposals;
    mapping(address => mapping(uint256 => Receipt)) public proposalReceipts;
    mapping(address => uint256) public latestProposalIds;
    mapping(address => bool) public emergencyExecutors;
    mapping(address => uint256) public lastVoteBlock;
    
    // ============ EVENTS ============
    
    event ProposalCreated(
        uint256 indexed proposalId,
        address indexed proposer,
        uint256 startTimestamp,
        uint256 endTimestamp,
        bool emergency,
        string title
    );

    // Final outcome is computed on-demand; no finalize event required
    
    event VoteCast(
        address indexed voter,
        uint256 indexed proposalId,
        uint8 support,
        uint256 votes,
        string reason
    );
    
    // Removed two-step execution; finalization emits ProposalFinalized
    event ProposalCanceled(uint256 indexed proposalId);
    event EmergencyModeToggled(bool enabled);
    event EmergencyExecutorUpdated(address indexed executor, bool enabled);
    
    // ============ MODIFIERS ============
    
    modifier onlyProposer(uint256 proposalId) {
        require(
            proposals[proposalId].proposer == msg.sender,
            "CompleteDAOGovernance: caller is not the proposer"
        );
        _;
    }
    
    modifier onlyEmergencyExecutor() {
        require(
            emergencyExecutors[msg.sender],
            "CompleteDAOGovernance: caller is not emergency executor"
        );
        _;
    }
    
    modifier notEmergencyMode() {
        require(!emergencyMode, "CompleteDAOGovernance: emergency mode active");
        _;
    }
    
    modifier onlyInEmergencyMode() {
        require(emergencyMode, "CompleteDAOGovernance: not in emergency mode");
        _;
    }
    
    // ============ CONSTRUCTOR ============
    
    constructor(address _governanceToken, address _stakingContract) {
        governanceToken = IERC20(_governanceToken);
        stakingContract = IStakingContract(_stakingContract);
        totalSupply = governanceToken.totalSupply();
        
        // Set deployer as emergency executor initially
        emergencyExecutors[msg.sender] = true;
    }
    
    // ============ VIEW FUNCTIONS ============
    
    function getVotingPower(address account) public view returns (uint256) {
        // Voting power equals only staked tokens (1:1 with staked amount)
        return getStakedAmount(account);
    }
    
    function getStakedAmount(address account) public view returns (uint256) {
        (uint256 totalStaked, , , , ) = stakingContract.Stakers(account);
        return totalStaked;
    }
    
    function canPropose(address account) public view returns (bool) {
        uint256 votingPower = getVotingPower(account);
        uint256 threshold = totalSupply.mul(proposalThresholdPercent).div(BASIS_POINTS);
        return votingPower >= threshold;
    }
    
    function state(uint256 proposalId) public view returns (uint8) {
        require(proposalCount >= proposalId, "CompleteDAOGovernance: invalid proposal id");
        
        Proposal storage proposal = proposals[proposalId];
        
        if (proposal.canceled) {
            return 2; // Canceled
        }
        
        if (block.timestamp <= proposal.startTimestamp) {
            return 0; // Pending
        }
        
        if (block.timestamp <= proposal.endTimestamp) {
            return 1; // Active
        }
        
        // After endTimestamp: Completed (result computable on-demand)
        return 5; // Executed-equivalent (completed)
    }
    
    function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory) {
        return proposalReceipts[voter][proposalId];
    }
    
    function hasQuorum(uint256 proposalId) public view returns (bool) {
        Proposal storage proposal = proposals[proposalId];
        uint256 totalVotes = proposal.forVotes.add(proposal.againstVotes).add(proposal.abstainVotes);
        uint256 quorumRequired = totalSupply.mul(quorumPercentBps).div(BASIS_POINTS);
        return totalVotes >= quorumRequired;
    }
    
    // ============ PROPOSAL FUNCTIONS ============
    
    function propose(
        string memory title,
        bool emergency
    ) external returns (uint256) {
        require(canPropose(msg.sender), "CompleteDAOGovernance: proposer votes below threshold");
        
        if (emergency) {
            require(emergencyMode, "CompleteDAOGovernance: emergency mode not active");
        }
        
        if (latestProposalIds[msg.sender] != 0) {
            uint8 proposersLatestProposalState = state(latestProposalIds[msg.sender]);
            require(proposersLatestProposalState != 1, "CompleteDAOGovernance: one live proposal per proposer");
            require(proposersLatestProposalState != 0, "CompleteDAOGovernance: one live proposal per proposer");
        }
        
        proposalCount++;
        proposals[proposalCount].id = proposalCount;
        proposals[proposalCount].proposer = msg.sender;
        proposals[proposalCount].startTimestamp = block.timestamp + VOTING_DELAY;
        proposals[proposalCount].endTimestamp = proposals[proposalCount].startTimestamp + (
            emergency ? EMERGENCY_VOTING_PERIOD : VOTING_PERIOD
        );
        proposals[proposalCount].emergency = emergency;
        proposals[proposalCount].title = title;
        
        latestProposalIds[msg.sender] = proposals[proposalCount].id;
        
        emit ProposalCreated(
            proposals[proposalCount].id,
            msg.sender,
            proposals[proposalCount].startTimestamp,
            proposals[proposalCount].endTimestamp,
            emergency,
            title
        );
        
        return proposals[proposalCount].id;
    }
    
    // No finalize: outcome computed on-demand via view
    
    function cancel(uint256 proposalId) external onlyProposer(proposalId) {
        require(state(proposalId) < 5, "CompleteDAOGovernance: cannot cancel completed proposal");
        
        Proposal storage proposal = proposals[proposalId];
        proposal.canceled = true;
        
        emit ProposalCanceled(proposalId);
    }

    // Admin-only hard delete (removes storage to save gas for future ops)
    function deleteProposal(uint256 proposalId) external onlyEmergencyExecutor {
        require(state(proposalId) != 1, "CompleteDAOGovernance: cannot delete active proposal");
        delete proposals[proposalId];
    }
    
    // ============ VOTING FUNCTIONS ============
    
    function castVote(uint256 proposalId, uint8 support) external {
        _castVote(msg.sender, proposalId, support, "");
    }
    
    function castVoteWithReason(uint256 proposalId, uint8 support, string calldata reason) external {
        _castVote(msg.sender, proposalId, support, reason);
    }
    
    function _castVote(address voter, uint256 proposalId, uint8 support, string memory reason) internal {
        require(state(proposalId) == 1, "CompleteDAOGovernance: voting is closed");
        require(support <= 2, "CompleteDAOGovernance: invalid vote type");
        require(lastVoteBlock[voter] != block.number, "CompleteDAOGovernance: already voted this block");
        
        Proposal storage proposal = proposals[proposalId];
        Receipt storage receipt = proposalReceipts[voter][proposalId];
        
        require(!receipt.hasVoted, "CompleteDAOGovernance: voter already voted");
        
        uint256 votes = getVotingPower(voter);
        require(votes > 0, "CompleteDAOGovernance: no voting power");
        
        receipt.hasVoted = true;
        receipt.support = support;
        receipt.votes = votes;
        lastVoteBlock[voter] = block.number;
        
        if (support == 0) {
            proposal.againstVotes = proposal.againstVotes.add(votes);
        } else if (support == 1) {
            proposal.forVotes = proposal.forVotes.add(votes);
        } else if (support == 2) {
            proposal.abstainVotes = proposal.abstainVotes.add(votes);
        }
        
        emit VoteCast(voter, proposalId, support, votes, reason);
    }
    
    // ============ EMERGENCY FUNCTIONS ============
    
    function toggleEmergencyMode() external onlyEmergencyExecutor {
        emergencyMode = !emergencyMode;
        emit EmergencyModeToggled(emergencyMode);
    }
    
    function setEmergencyExecutor(address executor, bool enabled) external onlyEmergencyExecutor {
        emergencyExecutors[executor] = enabled;
        emit EmergencyExecutorUpdated(executor, enabled);
    }
    
    function emergencyExecute(
        address target,
        uint256 value,
        bytes calldata data
    ) external onlyEmergencyExecutor onlyInEmergencyMode {
        (bool success, ) = target.call{value: value}(data);
        require(success, "CompleteDAOGovernance: emergency execution failed");
    }
    
    // ============ ADMIN FUNCTIONS ============
    
    function updateTotalSupply() external {
        totalSupply = governanceToken.totalSupply();
    }
    
    function emergencyWithdraw() external onlyEmergencyExecutor onlyInEmergencyMode {
        uint256 balance = address(this).balance;
        if (balance > 0) {
            (bool success, ) = msg.sender.call{value: balance}("");
            require(success, "CompleteDAOGovernance: emergency withdrawal failed");
        }
    }
    
    /**
     * @notice Update quorum percentage in basis points (e.g., 2500 = 25%)
     * @dev Restricted to emergency executors; consider gating via governance in production
     */
    /**
     * @notice Update proposal threshold percentage in basis points (e.g., 1 = 0.01%)
     * @dev Restricted to emergency executors; consider gating via governance in production
     */
    function setProposalThresholdPercent(uint256 newThresholdPercent) external onlyEmergencyExecutor {
        require(newThresholdPercent <= BASIS_POINTS, "CompleteDAOGovernance: invalid threshold bps");
        proposalThresholdPercent = newThresholdPercent;
    }
    
    function setQuorumPercentBps(uint256 newQuorumBps) external onlyEmergencyExecutor {
        require(newQuorumBps <= BASIS_POINTS, "CompleteDAOGovernance: invalid bps");
        quorumPercentBps = newQuorumBps;
    }
    
    // ============ UTILITY FUNCTIONS ============
    
    // removed on-chain action getters; proposals are off-chain signaling only
    
    function getProposalTitle(uint256 proposalId) external view returns (string memory) {
        return proposals[proposalId].title;
    }

    function getOutcome(uint256 proposalId) external view returns (
        bool isCompleted,
        bool quorumMet,
        bool passed,
        uint256 forVotes,
        uint256 againstVotes,
        uint256 abstainVotes
    ) {
        Proposal storage p = proposals[proposalId];
        isCompleted = block.timestamp > p.endTimestamp || p.canceled;
        forVotes = p.forVotes;
        againstVotes = p.againstVotes;
        abstainVotes = p.abstainVotes;
        quorumMet = hasQuorum(proposalId);
        passed = (forVotes > againstVotes) && quorumMet && !p.canceled && isCompleted;
    }
}

Tags:
ERC20, Multisig, Voting, Multi-Signature, Factory|addr:0x76411cec2ff4dab4cb2a63f11771ac172b3d106f|verified:true|block:23585070|tx:0xbcf6d41024b1f57e7ac49a586cb78178a3efac80701ccba6d1502961b93f5a0f|first_check:1760598673

Submitted on: 2025-10-16 09:11:16

Comments

Log in to comment.

No comments yet.