Skip to content
Snippets Groups Projects
Commit 85314267 authored by Robert Martin-Legene's avatar Robert Martin-Legene
Browse files

Generic majority voting system.

parent 16054aba
No related branches found
No related tags found
No related merge requests found
// Robert Martin-Legene <robert@nic.ar>
// vim:syntax:filetype=javascript:ai:sm
pragma solidity ^0.4.24;
// This contract is supposed to maintain the list of authorized sealers.
// For this to work, a sealer must deploy the contract and then all sealers
// must, say, every 5 minutes, run the related SealerSync.js
contract Sealers {
// This contract is supposed to maintain a list of accounts authorized
// to control members of "the club" using a majority (n/1+1).
// For instance, could be useful for
// - a list of sealers
// - a board of directors
// - a local chess club
// - even outsourcing this kind of management from another smart contract.
contract Majority {
// This struct contains a list of votes and who has voted for each victim/beneficiary.
// Votes taking longer than 30K blocks, will have to restart.
struct Proposal {
......@@ -16,41 +20,41 @@ contract Sealers {
bool promotion; // true=promotion, false=demotion
address[] voters; // List of voters.
}
address[] private sealers;
Proposal[] private sealerproposals;
address[] public voters;
Proposal[] public voterproposals;
event voteCast( address voter, address victim, bool promotionOrDemotion );
event adminChange( address admin, bool promotionOrDemotion );
constructor() public
{
sealers.push( msg.sender );
voters.push( msg.sender );
}
// This function is used to know how many sealers are in the sealers list.
function sealerLength() public view returns (uint)
// This function is used to know how many voters exist.
function votersLength() public view returns (uint)
{
return sealers.length;
return voters.length;
}
// This function is to be used to get the address from a specific position in the sealers list.
// This function is to be used to get the address from a specific position in the list.
// Remember first position is 0.
// Last position is sealers.length-1.
function sealerPosition( uint idx ) public view returns (address)
// Last position is voters.length-1.
function voterPosition( uint idx ) public view returns (address)
{
require( idx < sealers.length, "There are not that many sealers registered in the list." );
return sealers[idx];
require( idx < voters.length, "There are not that many entries in the list." );
return voters[idx];
}
function isSealer( address subject ) public view returns (bool)
function isVoter( address subject ) public view returns (bool)
{
return ( _findAddressInList( sealers, subject ) > 0 );
return ( _findAddressInList( voters, subject ) > 0 );
}
// Returns an index to the position of the needle inside haystack.
// Beware that:
// 0 = not found.
// 1 = first position.
// 1 = first position in a list (so this is actually the real list position + 1).
function _findAddressInList( address[] haystack, address needle ) private pure returns (uint)
{
uint i = haystack.length;
......@@ -64,34 +68,34 @@ contract Sealers {
{
// Move all items in the list (after idx) one step closer to the
// front of the list.
uint max = sealerproposals.length;
uint max = voterproposals.length;
while ( ++idx < max )
sealerproposals[idx-1] = sealerproposals[idx];
voterproposals[idx-1] = voterproposals[idx];
// "pop" the end of the list, making the list shorter.
sealerproposals.length--;
voterproposals.length--;
}
function _del_votes( address victim ) private
function _del_votes( address victim ) private
{
uint i_max = sealerproposals.length;
uint i_max = voterproposals.length;
uint i = i_max;
// check all proposals
while ( i-- > 0 )
{
// check all voters for every proposal
uint j_max = sealerproposals[i].voters.length;
uint j_max = voterproposals[i].voters.length;
uint j = j_max;
while ( j-- > 0 )
if ( sealerproposals[i].voters[j] == victim )
if ( voterproposals[i].voters[j] == victim )
{
// Found the victim as voter, but since he is about
// to be deleted, we will remove him from the list.
uint k = j;
while ( ++k < j_max )
sealerproposals[i].voters[k-1] = sealerproposals[i].voters[k];
sealerproposals[i].voters.length--;
voterproposals[i].voters[k-1] = voterproposals[i].voters[k];
voterproposals[i].voters.length--;
j_max--;
if ( sealerproposals[i].voters.length == 0 )
if ( voterproposals[i].voters.length == 0 )
{
_remove_proposal( i );
i_max--;
......@@ -100,69 +104,70 @@ contract Sealers {
}
}
function _remove_sealer( address victim ) private
function _remove_voter( address victim ) private
{
// Remove votes that the victim has already cast.
_del_votes( victim );
// Move all items in the list (after match) one step closer to the
// front of the list.
uint max = sealers.length;
uint max = voters.length;
uint i = max;
while ( i-- > 0 )
if ( sealers[i] == victim )
if ( voters[i] == victim )
{
// We could have recycled 'i' here, but for clarity, we don't.
uint j = i;
while ( ++j < max )
sealers[j-1] = sealers[j];
voters[j-1] = voters[j];
// "pop" the end of the list, making the list shorter.
sealers.length--;
voters.length--;
return;
}
}
// This function sees if any proposals have overstayed their welcome and thus
// needs to be removed.
// needs to be removed. This is a bit like spring cleaning. We remove proposals
// that are over 30k blocks old.
function _trimProposals() private
{
uint i = sealerproposals.length;
uint i = voterproposals.length;
while ( i-- > 0 )
{
// If a proposal is more than 30K blocks old, then remove it from the list.
if ( sealerproposals[i].votestart + 30000 <= block.number )
if ( voterproposals[i].votestart + 30000 <= block.number )
_remove_proposal( i );
}
}
// We run through the entire list of proposals, checking if they fulfill the
// requirements. Why the whole list? Because if a sealer is removed, whom has
// requirements. Why the whole list? Because if a voter has been removed, whom has
// not yet voted for a proposal, that proposal may now have achieved majority.
function _promotedemote() private
{
uint prevlength = 0;
// Keep looping over the list until the number of proposals stops changing.
while ( prevlength != sealerproposals.length )
while ( prevlength != voterproposals.length )
{
uint i = sealerproposals.length;
uint i = voterproposals.length;
prevlength = i;
uint majority = sealers.length / 2 + 1;
uint majority = voters.length / 2 + 1;
// Loop over all proposals
while ( i-- > 0 )
{
// If we have enough votes to perform the actual promotion/demotion
if ( sealerproposals[i].voters.length >= majority )
if ( voterproposals[i].voters.length >= majority )
{
// Is it a promotion or a demotion?
if ( sealerproposals[i].promotion )
// Add victim to sealer list
sealers.push( sealerproposals[i].victim );
if ( voterproposals[i].promotion )
// Add victim to member list
voters.push( voterproposals[i].victim );
else
// Remove victim from sealer list
_remove_sealer( sealerproposals[i].victim );
// Remove victim from member list
_remove_voter( voterproposals[i].victim );
// Send notification
emit adminChange( sealerproposals[i].victim,
sealerproposals[i].promotion );
emit adminChange( voterproposals[i].victim,
voterproposals[i].promotion );
// Remove the proposal because the voting is complete.
_remove_proposal( i );
}
......@@ -175,9 +180,9 @@ contract Sealers {
// 1 = first position.
function promotionIdx( address victim, bool promotion ) public view returns (uint)
{
uint i = sealerproposals.length;
uint i = voterproposals.length;
while ( i-- > 0)
if ( sealerproposals[i].victim == victim && sealerproposals[i].promotion == promotion )
if ( voterproposals[i].victim == victim && voterproposals[i].promotion == promotion )
return i+1;
return 0;
}
......@@ -186,24 +191,22 @@ contract Sealers {
// will get accepted. Save the network, call this first.
function mayVote( address voter, address victim, bool promotion ) public view returns (bool)
{
// Is caller a sealer?
if ( !isSealer(voter) )
// Is caller a voter?
if ( !isVoter(voter) )
return false;
// Is already Sealer and want to promote him?
// Can't promote someone who is already a sealer.
bool victimSealer = isSealer( victim );
if ( victimSealer && promotion )
// Can't promote someone who is already a voter.
bool victimVoter = isVoter( victim );
if ( victimVoter && promotion )
return false;
// Is not Sealer and want to demote him?
// Can't demote someone who is not a sealer.
if ( !victimSealer && !promotion )
// Can't demote someone who is not a member.
if ( !victimVoter && !promotion )
return false;
// See if the voter is already in the list of voters for this [victim,promotion] tuple
uint proppos = promotionIdx( victim, promotion );
if ( proppos > 0 && _findAddressInList( sealerproposals[proppos-1].voters, voter ) > 0 )
if ( proppos > 0 && _findAddressInList( voterproposals[proppos-1].voters, voter ) > 0 )
return false;
return true;
}
......@@ -211,7 +214,7 @@ contract Sealers {
// Calling this function will vote for adding an additional or
// removing an existing member of "the inner circle".
// As per usual, this requires n/2+1 votes.
// The boolean must be true if you want to add a sealer
// The boolean must be true if you want to add a member
// and false if you want to remove one.
function vote( address victim, bool promotion ) public
{
......@@ -227,18 +230,18 @@ contract Sealers {
{
// This is a new proposal, so we will add it, so we can vote on it.
address[] memory emptyAddressList;
proppos = sealerproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) );
proppos = voterproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) );
}
proppos--;
// Add our vote
sealerproposals[proppos].voters.push( msg.sender );
voterproposals[proppos].voters.push( msg.sender );
// See if we're ready to promote/demote anyone, based on the proposals.
_promotedemote();
// If we have no more sealers, we have no reason to live.
if ( sealers.length == 0 )
// If we have no more voters, we have no reason to live.
if ( voters.length == 0 )
selfdestruct( msg.sender );
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment