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;
}
}
Submitted on: 2025-10-16 09:11:16
Comments
Log in to comment.
No comments yet.