The ethereum.org democracy tutorial: token holders propose a transaction, vote on it for a fixed period, and the contract sends it if it passes.
Historical Significance
This is the pattern The DAO was built on, reduced to something one person could deploy: votes weighted by token balance, a fixed debating period, and a passing proposal that executes as a raw call from the treasury. The tutorial also shows the defence that mattered, committing to a hash of the transaction when the proposal opens so that voters and executor cannot be shown different code. Weighing votes at execution time rather than at voting time is the flaw it kept: a voter can sell their shares and their vote still counts.
Context
The democracy tutorial sat on ethereum.org beside the token and crowdsale tutorials and was revised repeatedly through 2016. Copies of it were deployed both before and after The DAO was drained in June 2016, which suggests it was read as a worked example of shareholder voting rather than as a warning.
Key Facts
Description
The democracy contract from the ethereum.org tutorial: an association whose members are whoever holds a balance in a separate token, and whose decisions are transactions the association itself sends.
Any holder may open a proposal naming a recipient, an amount in whole ether, a text description and the bytecode of the transaction to send. What is stored is a hash of the recipient, the amount and that bytecode, so the exact transaction being voted on is committed in advance and cannot be swapped afterwards. Holders then vote for or against until the debating period expires, and each address may vote once.
After the deadline anyone may execute the proposal, supplying the bytecode again for the contract to check against the stored hash. Votes are weighed by the token balance each voter holds at that moment, not at the time they voted, and the tally has to clear a minimum quorum measured in shares before it counts. If more weight is in favour than against, the association makes the call with the proposed value attached.
Its shares token, quorum and debating period were fixed when it was deployed and can be read from the contract itself. It is the later revision of the tutorial, which also accepts approveAndCall from a token so shares can be delivered and registered in one transaction.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Spurious Dragon Era
Continued DoS protection. State trie clearing.
Bytecode Overview
Verified Source Available
This contract has verified source code on Etherscan.
Show source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
pragma solidity ^0.4.2;
/* The token is used as a voting shares */
contract token { mapping (address => uint256) public balanceOf; }
/* define 'owned' */
contract owned {
address public owner;
function owned() {
owner = msg.sender;
}
modifier onlyOwner {
if (msg.sender != owner) throw;
_;
}
function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}
}
contract tokenRecipient {
event receivedEther(address sender, uint amount);
event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);
function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData){
Token t = Token(_token);
if (!t.transferFrom(_from, this, _value)) throw;
receivedTokens(_from, _value, _token, _extraData);
}
function () payable {
receivedEther(msg.sender, msg.value);
}
}
contract Token {
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}
/* The democracy contract itself */
contract Association is owned, tokenRecipient {
/* Contract Variables and events */
uint public minimumQuorum;
uint public debatingPeriodInMinutes;
Proposal[] public proposals;
uint public numProposals;
token public sharesTokenAddress;
event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
event Voted(uint proposalID, bool position, address voter);
event ProposalTallied(uint proposalID, uint result, uint quorum, bool active);
event ChangeOfRules(uint minimumQuorum, uint debatingPeriodInMinutes, address sharesTokenAddress);
struct Proposal {
address recipient;
uint amount;
string description;
uint votingDeadline;
bool executed;
bool proposalPassed;
uint numberOfVotes;
bytes32 proposalHash;
Vote[] votes;
mapping (address => bool) voted;
}
struct Vote {
bool inSupport;
address voter;
}
/* modifier that allows only shareholders to vote and create new proposals */
modifier onlyShareholders {
if (sharesTokenAddress.balanceOf(msg.sender) == 0) throw;
_;
}
/* First time setup */
function Association(token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) payable {
changeVotingRules(sharesAddress, minimumSharesToPassAVote, minutesForDebate);
}
/*change rules*/
function changeVotingRules(token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) onlyOwner {
sharesTokenAddress = token(sharesAddress);
if (minimumSharesToPassAVote == 0 ) minimumSharesToPassAVote = 1;
minimumQuorum = minimumSharesToPassAVote;
debatingPeriodInMinutes = minutesForDebate;
ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, sharesTokenAddress);
}
/* Function to create a new proposal */
function newProposal(
address beneficiary,
uint etherAmount,
string JobDescription,
bytes transactionBytecode
)
onlyShareholders
returns (uint proposalID)
{
proposalID = proposals.length++;
Proposal p = proposals[proposalID];
p.recipient = beneficiary;
p.amount = etherAmount;
p.description = JobDescription;
p.proposalHash = sha3(beneficiary, etherAmount, transactionBytecode);
p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;
p.executed = false;
p.proposalPassed = false;
p.numberOfVotes = 0;
ProposalAdded(proposalID, beneficiary, etherAmount, JobDescription);
numProposals = proposalID+1;
return proposalID;
}
/* function to check if a proposal code matches */
function checkProposalCode(
uint proposalNumber,
address beneficiary,
uint etherAmount,
bytes transactionBytecode
)
constant
returns (bool codeChecksOut)
{
Proposal p = proposals[proposalNumber];
return p.proposalHash == sha3(beneficiary, etherAmount, transactionBytecode);
}
/* */
function vote(uint proposalNumber, bool supportsProposal)
onlyShareholders
returns (uint voteID)
{
Proposal p = proposals[proposalNumber];
if (p.voted[msg.sender] == true) throw;
voteID = p.votes.length++;
p.votes[voteID] = Vote({inSupport: supportsProposal, voter: msg.sender});
p.voted[msg.sender] = true;
p.numberOfVotes = voteID +1;
Voted(proposalNumber, supportsProposal, msg.sender);
return voteID;
}
function executeProposal(uint proposalNumber, bytes transactionBytecode) {
Proposal p = proposals[proposalNumber];
/* Check if the proposal can be executed */
if (now < p.votingDeadline /* has the voting deadline arrived? */
|| p.executed /* has it been already executed? */
|| p.proposalHash != sha3(p.recipient, p.amount, transactionBytecode)) /* Does the transaction code match the proposal? */
throw;
/* tally the votes */
uint quorum = 0;
uint yea = 0;
uint nay = 0;
for (uint i = 0; i < p.votes.length; ++i) {
Vote v = p.votes[i];
uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
quorum += voteWeight;
if (v.inSupport) {
yea += voteWeight;
} else {
nay += voteWeight;
}
}
/* execute result */
if (quorum <= minimumQuorum) {
/* Not enough significant voters */
throw;
} else if (yea > nay ) {
/* has quorum and was approved */
p.executed = true;
if (!p.recipient.call.value(p.amount * 1 ether)(transactionBytecode)) {
throw;
}
p.proposalPassed = true;
} else {
p.proposalPassed = false;
}
// Fire Events
ProposalTallied(proposalNumber, yea - nay, quorum, p.proposalPassed);
}
}External Links
Related contracts
Contract 0x24fe45...1837fc
Same eraThe public counter that sells permissioned storage deployments for a fee
0x24fe45...1837fcNovember 23, 2016Crowdsale
Same eraThe ethereum.org crowdsale tutorial: contributions run to a deadline against a goal, pay out a separate reward token, and are refunded if the goal is missed.
0xf90503...bf62efNovember 23, 2016Contract 0xf8b094...41732a
Same eraThe public counter that sells mintable token deployments for a fee
0xf8b094...41732aNovember 24, 2016Contract 0xad8d30...369f49
Same eraThe public counter that sells DAO container deployments for a fee
0xad8d30...369f49November 24, 2016TokenTraderFactory
Same eraThe factory behind the TokenTrader contracts: it deploys a trader per asset and price, hands it to the caller, and vouches for what it built.
0x780a5c...600d50November 26, 2016Contract 0xb4ba41...2509fe
Same eraThe public counter that sells congress governance deployments for a fee
0xb4ba41...2509feNovember 28, 2016