Solidity Project: DAO Voting
A DAO voting project is a contract where members propose ideas and vote on them as a group. A proposal passes when more members vote for it than against it.
A member proposal starts with zero votes
A member proposes an idea and it gets the next proposal id. Its votes for and against both start at zero.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleDAO {
address public owner;
mapping(address => bool) public members;
uint256 public proposalCount;
struct Proposal {
string description;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
constructor() {
owner = msg.sender;
members[msg.sender] = true;
}
function addMember(address member) public {
require(msg.sender == owner, "only owner adds members");
members[member] = true;
}
function propose(string memory description) public returns (uint256) {
require(members[msg.sender], "only members propose");
uint256 id = proposalCount;
proposals[id].description = description;
proposalCount += 1;
return id;
}
function vote(uint256 id, bool support) public {
require(members[msg.sender], "only members vote");
require(id < proposalCount, "no such proposal");
require(!hasVoted[id][msg.sender], "already voted");
hasVoted[id][msg.sender] = true;
if (support) {
proposals[id].votesFor += 1;
} else {
proposals[id].votesAgainst += 1;
}
}
function execute(uint256 id) public {
require(members[msg.sender], "only members execute");
require(id < proposalCount, "no such proposal");
require(!proposals[id].executed, "already executed");
require(proposals[id].votesFor > proposals[id].votesAgainst, "needs more votes for");
proposals[id].executed = true;
}
}
A vote counts once per member
A member votes for a proposal and the votes for count rises to one. The same member voting again reverts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleDAO {
address public owner;
mapping(address => bool) public members;
uint256 public proposalCount;
struct Proposal {
string description;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
constructor() {
owner = msg.sender;
members[msg.sender] = true;
}
function addMember(address member) public {
require(msg.sender == owner, "only owner adds members");
members[member] = true;
}
function propose(string memory description) public returns (uint256) {
require(members[msg.sender], "only members propose");
uint256 id = proposalCount;
proposals[id].description = description;
proposalCount += 1;
return id;
}
function vote(uint256 id, bool support) public {
require(members[msg.sender], "only members vote");
require(id < proposalCount, "no such proposal");
require(!hasVoted[id][msg.sender], "already voted");
hasVoted[id][msg.sender] = true;
if (support) {
proposals[id].votesFor += 1;
} else {
proposals[id].votesAgainst += 1;
}
}
function execute(uint256 id) public {
require(members[msg.sender], "only members execute");
require(id < proposalCount, "no such proposal");
require(!proposals[id].executed, "already executed");
require(proposals[id].votesFor > proposals[id].votesAgainst, "needs more votes for");
proposals[id].executed = true;
}
}
A non-member vote reverts
Only members may vote on a proposal. A stranger voting on the same proposal reverts and the count stays put.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleDAO {
address public owner;
mapping(address => bool) public members;
uint256 public proposalCount;
struct Proposal {
string description;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
constructor() {
owner = msg.sender;
members[msg.sender] = true;
}
function addMember(address member) public {
require(msg.sender == owner, "only owner adds members");
members[member] = true;
}
function propose(string memory description) public returns (uint256) {
require(members[msg.sender], "only members propose");
uint256 id = proposalCount;
proposals[id].description = description;
proposalCount += 1;
return id;
}
function vote(uint256 id, bool support) public {
require(members[msg.sender], "only members vote");
require(id < proposalCount, "no such proposal");
require(!hasVoted[id][msg.sender], "already voted");
hasVoted[id][msg.sender] = true;
if (support) {
proposals[id].votesFor += 1;
} else {
proposals[id].votesAgainst += 1;
}
}
function execute(uint256 id) public {
require(members[msg.sender], "only members execute");
require(id < proposalCount, "no such proposal");
require(!proposals[id].executed, "already executed");
require(proposals[id].votesFor > proposals[id].votesAgainst, "needs more votes for");
proposals[id].executed = true;
}
}
A winning proposal executes, a losing one cannot
One vote for and none against lets the proposal execute. A proposal with more votes against it cannot execute.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleDAO {
address public owner;
mapping(address => bool) public members;
uint256 public proposalCount;
struct Proposal {
string description;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
constructor() {
owner = msg.sender;
members[msg.sender] = true;
}
function addMember(address member) public {
require(msg.sender == owner, "only owner adds members");
members[member] = true;
}
function propose(string memory description) public returns (uint256) {
require(members[msg.sender], "only members propose");
uint256 id = proposalCount;
proposals[id].description = description;
proposalCount += 1;
return id;
}
function vote(uint256 id, bool support) public {
require(members[msg.sender], "only members vote");
require(id < proposalCount, "no such proposal");
require(!hasVoted[id][msg.sender], "already voted");
hasVoted[id][msg.sender] = true;
if (support) {
proposals[id].votesFor += 1;
} else {
proposals[id].votesAgainst += 1;
}
}
function execute(uint256 id) public {
require(members[msg.sender], "only members execute");
require(id < proposalCount, "no such proposal");
require(!proposals[id].executed, "already executed");
require(proposals[id].votesFor > proposals[id].votesAgainst, "needs more votes for");
proposals[id].executed = true;
}
}
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract SimpleDAO {
address public owner;
mapping(address => bool) public members;
uint256 public proposalCount;
struct Proposal {
string description;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
constructor() {
owner = msg.sender;
members[msg.sender] = true;
}
function addMember(address member) public {
require(msg.sender == owner, "only owner adds members");
members[member] = true;
}
function propose(string memory description) public returns (uint256) {
require(members[msg.sender], "only members propose");
uint256 id = proposalCount;
proposals[id].description = description;
proposalCount += 1;
return id;
}
function vote(uint256 id, bool support) public {
require(members[msg.sender], "only members vote");
require(id < proposalCount, "no such proposal");
require(!hasVoted[id][msg.sender], "already voted");
hasVoted[id][msg.sender] = true;
if (support) {
proposals[id].votesFor += 1;
} else {
proposals[id].votesAgainst += 1;
}
}
function execute(uint256 id) public {
require(members[msg.sender], "only members execute");
require(id < proposalCount, "no such proposal");
require(!proposals[id].executed, "already executed");
require(proposals[id].votesFor > proposals[id].votesAgainst, "needs more votes for");
proposals[id].executed = true;
}
}
Key points
- DAO: a group that makes decisions by member votes recorded on a contract.
- Proposal: an idea a member puts forward for the group to decide.
- Vote: one member choice for or against a single proposal.
- One vote rule: each member votes at most once on each proposal.
- Execution: carrying out a proposal once it has passed.
- Majority: a proposal passes when votes for it outnumber votes against it.
In this example, a DAO with three members runs two proposals side by side. The first proposal wins two votes for and executes, while the second proposal draws only a vote against and stays unexecuted, and an executed proposal cannot execute a second time.
Note
the toy DAO counts members, not token weight, so every member counts the same.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Remix IDE
Exercise
A proposal has 2 votes for and 1 vote against. What happens when a member executes it?