Skip to content

Instantly share code, notes, and snippets.

@MidnightLightning
Last active May 11, 2021 16:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MidnightLightning/d3791c03c9fcdf3c2d935fcc8ea720ab to your computer and use it in GitHub Desktop.
Save MidnightLightning/d3791c03c9fcdf3c2d935fcc8ea720ab to your computer and use it in GitHub Desktop.
MoonCat DAO proposal

Mooncats have a unique situation where each and every one of them are uniquely identified and distinct from all the other Mooncats. In most smart-contract-based voting systems/DAOs, Ethereum addresses hold some number of tokens/shares and get some proportional voting weight. But then care has to be taken that in a given voting period for any proposal, that someone cannot vote with their shares, transfer the shares to another address, and then vote again. By having the cats be the voting unit rather than the addresses, MoonCats can solve this issue very neatly.

MoonCat Clubs

Mooncats have Red, Green, and Blue values that range from 0-255. Based on those parameters, they can be separated into different groups and vote as a unit.

Three MoonCat Clubs, one for each of Red, Green, and Blue. They each act as a DAO, where the voting rights on proposals are determined by the Red/Green/Blue values of the Cats.

The Club contracts operate like a DAO in that any member can put forth a proposal (in the form of a transaction to be executed on the blockchain), and all members can vote on it for a time, and after a specified voting time has passed, if successful, the Club contract sends that transaction. The upshot is that then the Club contract can be made the "owner" of other contracts (who grant their owner special rights), which effectively makes the child contract "owned" by the Club.

Membership

In order to belong to the Club at all, the cat's color value for that color must be greater than 127. If their color value is less than this, they can't create proposals or vote at all in the Club.

Genesis cats are granted membership automatically.

Voting weight

A cat's vote counts for (x-127)/8+1. Thus color values are grouped in chunks of 8, but a cat with a value of 255 gets one extra vote (17, compared to 254 which gets a vote weight of 16).

A cat gets an additional 5 points added to their vote weight if they are a named cat, and their name honors the mythical founder of the club (a specific string is found anywhere in their name).

Genesis cats have a vote weight of 20.

Clubs

Raskal's Regal Reds

  • Founder: Raskal Ratter-Ra
  • Key string: "rat" or "Rat"

Giggums' Glorious Greens

  • Founder: Sir Giggums Gobo III
  • Key string "gig" or "Gig"

Bessie's Beautiful Blues

  • Founder: Bessie Belle
  • Key string: "bel" or "Bel"

United Nations of MoonCats

One global DAO where each Mooncat has one vote on each proposal the DAO considers. Genesis cats are not treated differently in this DAO; they have one vote just like all the rest.

This organization serves as the check-and-balance to the other clubs and can serve as a general consensus for the whole community.

It consists of two Contracts, the MoonCat "Liquid Democracy" contract (following the model of the "Liquid Democracy" example DAO from Ethereum.org) and the MoonCat "United Nations" contract.

The Democracy contract serves as the Executive branch of the MoonCat community. Each MoonCat can nominate either themselves, or another MoonCat to serve as representative of the whole community. After analyzing all the nominations, the MoonCat that has the most nominations (a plurality of nominations) is designated High Cat, and is recognized as the representative of the whole MoonCat community.

The United Nations contract serves as the Legislative branch of the MoonCat community. Each MoonCat can create Proposals and Vote on them. The weight of each MoonCat's vote is the weight of the nominations that MoonCat has received in the Democracy contract. So even if an individual MoonCat is not the High Cat (the one with the highest weight), it can still have a large influence on the proposals put forth at the United Nations contract if it has a large number of nominations as well. Proposals that get a majority of the votes cast will be acted upon (as long as a minimum quorum of MoonCats voted).

Developers

If you wish to extend upon this government foundation and create contracts that are able to be acted-upon by the MoonCat community, simply pick which type of action is most approprate for your use-case.

To create a function that should be able to be enacted by "executive order" (a single individual, with no other oversight other than the nominations of the community to act on their best interest) the function should give special permission to transactions coming from (msg.sender) the Democracy contract (only the High Cat can make that contract trigger transactions, through the executiveOrder() function).

To create a function that the whole MoonCat community should vote on (like a bill passing through Congress) the function should give special permission to transactions coming from (msg.sender) the United Nations contract (the only way to trigger that contract sending a transaction is to have a Proposal be voted and approved by the community).

pragma solidity ^0.4.16;
contract CatDemocracy {
mapping (bytes5 => uint256) public voteWeight;
uint public numberOfDelegationRounds;
function balanceOf(bytes5 _catId) public constant returns (uint256 _balance) {
if (numberOfDelegationRounds < 3) {
return 0;
} else {
return voteWeight[_catId];
}
}
}
contract MoonCatToken {
mapping (bytes5 => address) public catOwners;
}
/**
* The shareholder association contract itself
*/
contract CatUnitedNations {
address public owner = msg.sender;
uint public minimumQuorum;
uint public debatingPeriodInMinutes;
Proposal[] public proposals;
uint public numProposals;
CatDemocracy public democracy;
MoonCatToken public moonCats;
event ProposalAdded(uint proposalID, address target, uint amount, string description);
event Voted(uint proposalID, bool position, bytes5 voter);
event ProposalTallied(uint proposalID, uint result, uint quorum, bool active);
event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, address newSharesTokenAddress);
struct Proposal {
address target;
uint amount;
string description;
uint votingDeadline;
bool executed;
bool proposalPassed;
uint numberOfVotes;
bytes32 proposalHash;
Vote[] votes;
mapping (bytes5 => bool) voted;
}
struct Vote {
bool inSupport;
bytes5 voter;
}
// Modifier that only allows the current owner to execute
modifier onlyOwner {
require(msg.sender == owner);
_;
}
/**
* Constructor function
*
* First time setup
*
* @param _token location of the MoonCatRescue contract
* @param _democracyAddress location of the LiquidCatDemocracy contract
* @param _minimumQuorum proposal can conclude only if the sum of shares held by all voters exceed this number
* @param _debatingPeriodInMinutes the minimum amount of delay between when a proposal is made and when it can be executed
*/
function CatUnitedNations(address _token, address _democracyAddress, uint _minimumQuorum, uint _debatingPeriodInMinutes) public payable {
changeVotingRules(_token, _democracyAddress, _minimumQuorum, _debatingPeriodInMinutes);
}
/**
* Set new owner for the contract
*
* @param _newOwner address of new owner
*/
function transferOwnership(address _newOwner) public onlyOwner {
owner = _newOwner;
}
/**
* Change voting rules
*
* Make so that proposals need tobe discussed for at least `minutesForDebate/60` hours
* and all voters combined must own more than `minimumSharesToPassAVote` shares of token `sharesAddress` to be executed
*
* @param _token location of the MoonCatRescue contract
* @param _democracyAddress location of the LiquidCatDemocracy contract
* @param _minimumQuorum proposal can conclude only if the sum of shares held by all voters exceed this number
* @param _debatingPeriodInMinutes the minimum amount of delay between when a proposal is made and when it can be executed
*/
function changeVotingRules(address _token, address _democracyAddress, uint _minimumQuorum, uint _debatingPeriodInMinutes) public onlyOwner {
moonCats = MoonCatToken(_token);
democracy = CatDemocracy(_democracyAddress);
if (_minimumQuorum == 0 ) _minimumQuorum = 1;
minimumQuorum = _minimumQuorum;
debatingPeriodInMinutes = _debatingPeriodInMinutes;
ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, democracy);
}
/**
* Add Proposal
*
* Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
*
* @param _catID the cat making the proposal
* @param _target who to send the ether to
* @param _weiAmount amount of ether to send, in wei
* @param _description Description of job
* @param _transactionBytecode bytecode of transaction
*/
function newProposal(
bytes5 _catID,
address _target,
uint _weiAmount,
string _description,
bytes _transactionBytecode
)
public
returns (uint proposalID)
{
require(moonCats.catOwners(_catID) == msg.sender); // Must be the owner of that cat
proposalID = proposals.length++;
Proposal storage p = proposals[proposalID];
p.target = _target;
p.amount = _weiAmount;
p.description = _description;
p.proposalHash = keccak256(_target, _weiAmount, _transactionBytecode);
p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;
p.executed = false;
p.proposalPassed = false;
p.numberOfVotes = 0;
ProposalAdded(proposalID, _target, _weiAmount, _description);
numProposals = proposalID+1;
return proposalID;
}
/**
* Check if a proposal code matches
*
* @param _proposalNumber ID number of the proposal to query
* @param _target who to send the ether to
* @param _weiAmount amount of ether to send
* @param _transactionBytecode bytecode of transaction
*/
function checkProposalCode(
uint _proposalNumber,
address _target,
uint _weiAmount,
bytes _transactionBytecode
)
public constant
returns (bool codeChecksOut)
{
Proposal storage p = proposals[_proposalNumber];
return p.proposalHash == keccak256(_target, _weiAmount, _transactionBytecode);
}
/**
* Log a vote for a proposal
*
* Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
*
* @param _catID The cat submitting the vote
* @param _proposalNumber number of proposal
* @param _supportsProposal either in favor or against it
*/
function vote(
bytes5 _catID,
uint _proposalNumber,
bool _supportsProposal
)
public
returns (uint voteID)
{
require(moonCats.catOwners(_catID) == msg.sender); // Must be the owner of that cat
Proposal storage p = proposals[_proposalNumber];
require(p.voted[_catID] != true);
voteID = p.votes.length++;
p.votes[voteID] = Vote({
inSupport: _supportsProposal,
voter: _catID
});
p.voted[_catID] = true;
p.numberOfVotes = voteID +1;
Voted(_proposalNumber, _supportsProposal, _catID);
return voteID;
}
/**
* Finish vote
*
* Count the votes proposal #`proposalNumber` and execute it if approved
*
* @param _proposalNumber proposal number
* @param _transactionBytecode optional: if the transaction contained a bytecode, you need to send it
*/
function executeProposal(uint _proposalNumber, bytes _transactionBytecode) public {
Proposal storage p = proposals[_proposalNumber];
require(now > p.votingDeadline // If it is past the voting deadline
&& !p.executed // and it has not already been executed
&& p.proposalHash == keccak256(p.target, p.amount, _transactionBytecode)); // and the supplied code matches the proposal...
// ...then tally the results
uint quorum = 0;
uint yea = 0;
uint nay = 0;
for (uint i = 0; i < p.votes.length; ++i) {
Vote storage v = p.votes[i];
uint voteWeight = democracy.balanceOf(v.voter);
quorum += voteWeight;
if (v.inSupport) {
yea += voteWeight;
} else {
nay += voteWeight;
}
}
require(quorum <= minimumQuorum); // Check if a minimum quorum has been reached
p.executed = true;
if (yea > nay) {
// Proposal passed; execute the transaction
require (p.target.call.value(p.amount)(_transactionBytecode));
p.proposalPassed = true;
} else {
// Proposal failed
p.proposalPassed = false;
}
// Fire Events
ProposalTallied(_proposalNumber, yea - nay, quorum, p.proposalPassed);
}
}
pragma solidity ^0.4.16;
contract MoonCatToken {
mapping (bytes5 => address) public catOwners;
}
contract LiquidCatDemocracy {
MoonCatToken public moonCats;
bool underExecution;
bytes5 public highCat;
mapping (bytes5 => uint) public voterId;
mapping (bytes5 => uint256) public voteWeight;
uint public delegatedPercent;
uint public lastWeightCalculation;
uint public numberOfDelegationRounds;
uint public outstandingNominations;
bool public isCalculating = false;
uint public numberOfVotes;
DelegatedVote[] public delegatedVotes;
string public forbiddenFunction;
event NewHighCat(bytes5 newHighCat, bool changed);
struct DelegatedVote {
bytes5 nominee;
bytes5 voter;
}
function LiquidCatDemocracy(
address _token,
string _forbiddenFunction,
uint _percentLossInEachRound
) public {
moonCats = MoonCatToken(_token);
delegatedVotes.length++;
delegatedVotes[0] = DelegatedVote({nominee: 0, voter: 0});
forbiddenFunction = _forbiddenFunction;
delegatedPercent = 100 - _percentLossInEachRound;
if (delegatedPercent > 100) delegatedPercent = 100;
}
function nominate(bytes5 _catId, bytes5 _nominee) public returns (uint voteIndex) {
require(moonCats.catOwners(_catId) == msg.sender // Must be the owner of that cat
&& !isCalculating); // and must not be in the middle of a recalculation
if (voterId[_catId] == 0) {
// This cat hasn't delegated its vote yet; create a new record
voterId[_catId] = delegatedVotes.length;
voteIndex = delegatedVotes.length++;
numberOfVotes = voteIndex;
} else {
voteIndex = voterId[_catId];
}
delegatedVotes[voteIndex] = DelegatedVote({nominee: _nominee, voter: _catId});
outstandingNominations++;
return voteIndex;
}
function executiveOrder(address _target, uint _valueInWei, bytes32 _bytecode) public {
require(moonCats.catOwners(highCat) == msg.sender // If caller is owner of the High Cat,
&& !underExecution // and the call is not currently being executed,
&& bytes4(_bytecode) != bytes4(keccak256(forbiddenFunction)) // and it's not trying to do the forbidden function
&& numberOfDelegationRounds >= 4); // and delegation has been calculated enough...
underExecution = true;
assert(_target.call.value(_valueInWei)(_bytecode)); // ...then execute the command.
underExecution = false;
}
function calculateVotes() public payable returns (bytes5 winner) {
bytes5 currentWinner = highCat;
uint currentMax = 0;
uint weight = 0;
DelegatedVote storage v = delegatedVotes[0];
if (outstandingNominations > 0) {
// Something's changed; we need to start calculations from the beginning
assert(now > lastWeightCalculation + 7 days); // Prevent spamming the recalculation
// Start the calculations over from scratch
numberOfDelegationRounds = 0;
isCalculating = true;
outstandingNominations = 0; // Reset counter for tracking changes to nominations
// Distribute the initial weight
for (uint i=1; i< delegatedVotes.length; i++) {
voteWeight[delegatedVotes[i].nominee] = 0;
}
for (i=1; i< delegatedVotes.length; i++) {
voteWeight[delegatedVotes[i].voter] = 1000; // Each cat gets 1000 vote weight, so the delegation percentage per round works
}
} else {
// Continue an existing calculation
numberOfDelegationRounds++;
uint lossRatio = 100 * delegatedPercent ** numberOfDelegationRounds / 100 ** numberOfDelegationRounds;
if (lossRatio > 0) {
for (i=1; i< delegatedVotes.length; i++){
v = delegatedVotes[i];
if (v.nominee != v.voter && voteWeight[v.voter] > 0) {
weight = voteWeight[v.voter] * lossRatio / 100;
voteWeight[v.voter] -= weight;
voteWeight[v.nominee] += weight;
}
if (numberOfDelegationRounds>3 && voteWeight[v.nominee] > currentMax) {
currentWinner = v.nominee;
currentMax = voteWeight[v.nominee];
}
}
}
if (numberOfDelegationRounds > 3) {
// Finalize the calculation
NewHighCat(currentWinner, highCat == currentWinner);
highCat = currentWinner;
if (isCalculating == true) {
lastWeightCalculation = now; // Reset timer for recalculating weights
isCalculating = false; // Enable submission of nominations again
}
}
}
return currentWinner;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment