diff --git a/src/contract.Sealers.solc b/src/contract.Sealers.solc index 03804744d7b7758a192b1e2c1f34a38271955344..47ae489fe24a48ddaea4b76ad6316d1e142cac45 100644 --- a/src/contract.Sealers.solc +++ b/src/contract.Sealers.solc @@ -1,15 +1,21 @@ pragma solidity ^0.4.22; +// 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 occasionally (every 5 minutes) run the related SealerSync.js + contract Sealers { + // 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 { address victim; // Whom are we voting about. - uint votestart; // In which block did this vote begin? + uint256 votestart; // In which block did this vote begin? bool promotion; // true=promotion, false=demotion address[] voters; // List of voters. } - Proposal[] public adminproposals; address[] public sealers; + Proposal[] public sealerproposals; event vote( address voter, address victim, bool promotionOrDemotion ); event adminChange( address admin, bool promotionOrDemotion ); @@ -18,107 +24,139 @@ contract Sealers { sealers[0] = msg.sender; } - function isSealer( address subj ) public view returns (bool) { - for (uint i=sealers.length; i>0; i--) - { - if (subj == sealers[i-1]) - return true; - } - return false; - } - modifier onlySealersTX { - require( isSealer( tx.origin ), "tx.origin is not a known sealer and can not call this function." ); - _; + // Public utility, works on any list needle and haystack given. + // + // Returns an index to the position of the needle inside haystack. + // 0 is first position. -1 means not found. + function findAddressInList( address[] haystack, address needle ) public pure returns (int256) { + uint256 i = haystack.length; + while ( i-- > 0 ) + if ( needle == haystack[i] ) + return int256(i & 0x7fffffffffffffffffffffffffffffff); + return -1; } - modifier onlySealers { - require( isSealer( msg.sender ), "msg.sender is not a known sealer and can not call this function." ); - _; + function sealerIdx( address needle ) public view returns (int256) { + return findAddressInList( sealers, needle ); } - - function sealerLength() public view returns (uint) { + // This function is to be used to know how many sealers are in the sealers list. + function sealerLength() public view returns (uint256) { return sealers.length; } - - function sealerPosition( uint idx ) public view returns (address) { - require( idx <= sealers.length, "There are not that many sealers registered in the list." ); + // This function is to be used to get the address from a specific position in the sealers list. + // Remember first position is 0. + // Last position is sealers.length-1. + function sealerPosition( uint256 idx ) public view returns (address) { + require( idx < sealers.length, "There are not that many sealers registered in the list." ); return sealers[idx]; } + function isSealer( address subject ) public view returns (bool) { + return ( sealerIdx(subject) > -1 ); + } + function requireSealer( address subject ) public view { + require( isSealer(subject), "Not sealer" ); + } + function _remove_entry( uint256 idx, list alist ) private returns (list) { + uint256 pos = idx; + // Move all items in the list (after idx) one step closer to the + // front of the list. + while ( ++pos < list.length ) + alist[pos-1] = alist[pos]; + // "pop" the end of the list, making the list shorter. + alist.length--; + return alist; + } + + // This function sees if any proposals have overstayed their welcome and thus + // needs to be removed. function _trimProposals() private { - uint i = adminproposals.length; - while ( i-- > 0 ) + for ( uint256 i = sealerproposals.length-1; i>=0; i-- ) { - if ( adminproposals[i].votestart < block.number+30000 ) - { - while ( i < adminproposals.length ) - { - adminproposals[i] = adminproposals[i+1]; - i--; - } - adminproposals.length--; - } + // If a proposal is more than 30K blocks old, then remove it from the list. + if ( sealerproposals[i].votestart + 30000 <= block.number ) + sealerproposals = _remove_entry( i, sealerproposals ); } } - function admin_promote( address victim, bool promotion ) public onlySealersTX { - // Janitor - _trimProposals(); - bool isS = isSealer(victim); + function promotionIdx( address victim, bool promotion ) public view return (int256) { + uint256 idx = sealerproposals.length; + while ( idx-- > 0) + if ( sealerproposals[idx].victim == victim && sealerproposals[idx].promotion == promotion ) + return idx; + return -1; + } + + // You can call this for free and know if your promote call + // 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) ) + return false; + // Is already Sealer and want to promote him? - if ( isS && promotion ) - revert("You can't promote someone who is already a sealer."); + // Can't promote someone who is already a sealer. + if ( isSealer(victim) && promotion ) + return false; + // Is not Sealer and want to demote him? - if ( !isS && !promotion ) - revert("You can't demote someone who is not a sealer."); - // First see if we can find the index of an already running proposal for this victim,promotion tupple. - uint proppos = adminproposals.length; - while ( proppos>0 && adminproposals[proppos-1].victim != victim && adminproposals[proppos-1].promotion != promotion) - proppos--; - // If we found no matching proposals, we will add it, so we can now vote on it. - if (proppos == 0) + // Can't demote someone who is not a sealer. + if ( !isSealer(victim) && !promotion ) + return false; + + // See if the voter is already in the list of voters for this [victim,promotion] tupple + int256 proppos = promotionIdx( victim, promotion ); + if ( proppos > -1 && findAddressInList( sealerproposals[proppos].voters, voter ) > -1 ) + return false; + return true; + } + + // 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 + // and false if you want to remove one. + function promote( address victim, bool promotion ) public { + if ( ! mayVote(msg.sender, victim, promotion)) + revert("That seems redundant."); + _trimProposals(); + + // Send notification of the vote + emit vote( msg.sender, victim, promotion ); + + // signed as opposed to unsigned (int) + int256 signedretval = promotionIdx( victim, promotion ); + uint proppos; + if ( signedretval == -1 ) { - address[] memory sillycompiler; - proppos = adminproposals.push( Proposal(victim, block.number, promotion, sillycompiler ) ); + // 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 ) ); } - // substract one, to make proppos actually reflect the position in the index. - proppos--; + else + proppos = uint256( signedretval & 0x7fffffffffffffffffffffffffffffff ); + // Make things a bit more easy to read by using a shorter variable name - Proposal memory prop = adminproposals[proppos]; - // Now look through the proposal and see if this sender already voted. - uint voterpos = prop.voters.length; - while ( voterpos>0 && prop.voters[voterpos-1] != msg.sender ) - voterpos--; - // Return if sender already voted - if ( voterpos > 0 ) - revert("You can not vote twice."); - // Send notification of the valid vote - emit vote( msg.sender, victim, promotion ); - // Do we have enough votes to perform the operation? + Proposal memory prop = sealerproposals[proppos]; + + // Add our vote + prop.voters.push( msg.sender ); + + // Stop here if we do not have enough votes to perform the actual promotion/demotion if ( prop.voters.length < sealers.length/2+1 ) return; - // + + // Remove the proposal because the voting is complete. + sealerproposals = _remove_entry( proppos, sealerproposals ); + // Is it a promotion or a demotion? - if ( prop.promotion ) - { + if ( promotion ) + // Add victim to sealer list sealers.push( victim ); - } else - { - uint i = sealers.length; - // Delete all occurences of the victim from the sealer list (should be just a single occurence) - while ( i-- > 0 ) { - if (sealers[i] == victim ) - { - for (uint j=i; j<sealers.length-1; j--) - sealers[j] = sealers[j+1]; - sealers.length--; - } - } - } - // Remove the proposal, as the voting is complete. - for (i=proppos; i<adminproposals.length-1; i--) - adminproposals[i] = adminproposals[i+1]; - adminproposals.length--; + // Remove victim from sealer list + sealers = _remove_entry( sealerIdx(victim), sealers ); + // Send notification emit adminChange( victim, promotion ); }