diff --git a/README.md b/README.md index 309c1918127a0caf60f1c7982280c9175d2af983..41eb652f63a749861c6938bcf5089953857ea44f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,13 @@ - If the text can not be found, it is because your insert transaction still isn't in the blockchain. Wait a bit and try again. - Try the basic `explorer.sh`. It follows "latest" by default, but you can specify a block number as argument, e.g. `explorer.sh 0` to see genesis (block 0). - Try out `walker.pl` +12. Install node.js so you can do better scripts locally: + `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 68576280` + `sudo apt-add-repository "deb https://deb.nodesource.com/node_7.x $(lsb_release -sc) main"` + `sudo apt-get update` + `sudo apt-get install nodejs` + `npm init -y` + `npm install web3` There are other "interesting" programs in the bin/ and src/ directories. diff --git a/bin/GasAdmin.js b/bin/GasAdmin.js new file mode 100755 index 0000000000000000000000000000000000000000..048d33896454672fa1c60ca2d634ecddebc73104 --- /dev/null +++ b/bin/GasAdmin.js @@ -0,0 +1,293 @@ +#!/usr/bin/node + +var port = 14349; // cloud +port = 16437; // bc +const networkdir = 'network5445'; +const readline = require('readline'); +const rl = readline.createInterface( { input: process.stdin, output: process.stdout } ); +const Web3 = require('web3'); +const fs = require('fs'); +const Personal = require('web3-eth-personal'); +var web3; +var GasWell; +var from; +var contractbalance; +var contractaddr; + +function fatal( txt ) +{ + console.log( txt ); + process.exit( 1 ); +} + +function init() +{ + if ( process.env.BFAHOME == undefined ) + fatal( "$BFAHOME not set. Did you source bfa/bin/env ?" ); + web3 = new Web3( "http://127.0.0.1:"+port ); + //accounts = new Accounts( web3 ); + var abi = ""; + contractaddr = fs.realpathSync([ process.env.BFAHOME, networkdir, 'contracts', 'GasWell' ].join('/')); + if ( contractaddr == undefined ) + fatal( "I can't seem to find the contract directory containing the Gas Well's ABI" ); + contractaddr = contractaddr.replace(/^.*\//, ''); + var abipath = [ process.env.BFAHOME, networkdir, 'contracts', contractaddr, 'abi' ].join('/'); + var abifile = fs.createReadStream( abipath ) + .on('readable', () => { + var data; + while (data = abifile.read()) + abi += data; + }) + .on('end', () => { + GasWell = new web3.eth.Contract( JSON.parse(abi), contractaddr ); + web3.eth.getBalance( contractaddr ).then( function(v){contractbalance=v} ); + getlist(); + }) + ; + getAccount(); +} + +function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + +function isAddr(n) { + return n.length == 42 && n.substring(0,2) == "0x"; +} + + +function getAccount() +{ + var personal = new Personal( web3 ); + personal.getAccounts().then( + function gotPersonalAccounts(obj) { + console.log("Setting default account to "+ obj[0]); + from = obj[0]; + web3.eth.defaultAccount = obj[0]; + }, + function failedGettingPersonalAccounts(err){ + fatal ("Did you remember to allow 'personal' via the RPC port? " + err) + } + ); +} + +function getpos(i) +{ + var arr; + var p = GasWell.methods.atPosition(i).call() + .then( + function gotOneAccount(result) { + console.log( "CC: "+result); + console.log( "C1: "+result[0]); + arr = result; + }, + function gotOneAccountError(err) { + console.log( "DD: "+err); + console.log(err); + } + ); + return arr; +} + +function wellSort(a,b) +{ + if ( b == undefined ) + { + if ( a == undefined ) + return 0; + else + return -1; + } + if ( a == undefined ) + return 1; + var strA = a[0].toLowerCase(); + var strB = b[0].toLowerCase(); + if ( strA < strB ) + return -1; + if ( strA > strB ) + return 1; + return 0; +} + +function requestBalances( count ) +{ + var well = new Array; + var proms = new Array; + var i; + for ( i=0; i<count; i++ ) + { + proms.push( + GasWell.methods.atPosition(i).call( {} ) + .then( + function(res){ + well.push(res); + }, + function(err) { console.log("Fetching position data failed: "+err) } + ) + ); + } + Promise.all( proms ).then( + function(x) { + // The Well has now been filled out + // so we will ask for the balances of the found accounts + var p2 = new Array(); + for ( i=0; i<count; i++ ) + { + var cb = setBal.bind(null,well,i); + p2.push( web3.eth.getBalance( well[i][0] ).then(cb) ); + } + Promise.all( p2 ).then( + function allbalances(a) { displayBalances( well ) }, + function(err) { console.log("Getting balances failed: "+err) } + ); + }, + function(err) { console.log("Getting account list failed: "+err) } + ); +} + +function setBal( arr, idx, val ) +{ + arr[idx][2]=val; +} + +function editAccount( entry, well ) +{ + if ( entry == undefined ) + return; + var acct; + var value; + // it is an existing account address? + if ( isAddr(entry) ) + { + var i = 0; + var n = well.length; + while ( i < n ) + { + if ( (""+well[i][0]).toLowerCase() == (""+entry).toLowerCase() ) + entry = i; + i++; + } + } + // it is a new account address? + if ( isAddr(entry) ) + { + acct = entry; + value = 0; + } + else + if ( isNumeric(entry) && entry < well.length ) + { + acct = well[entry][0]; + value = well[entry][1]; + } + else + if ( entry == "x" ) + { + // trigger Gas Well distribution + GasWell.methods.distribute().send( {"from": from, "gas": 1000000 } ) + .then( + function distOK(x) { + console.log("Distribute returned succesfully in block# "+x.blockNumber+" using "+x.gasUsed+" gas."); + getlist(); + }, + function distFail(x) { + console.log("Distribute returned errors in block# "+x.blockNumber+" using "+x.gasUsed+" gas."); + console.log(x); + process.exit( 1 ); + } + ); + return; + } + else + fatal("I don't know what to do with \""+entry+"\"." ); + rl.question("Adjust the Mwei fill value of "+acct+" (setting to 0 is the same as deleting)\nAmount?: ", (answer) => { + if ( isNumeric(answer) ) + { + answer *= 1000000; + console.log("Sending update to the Gas Well..."); + GasWell.methods.setEtherAllowance(acct,answer).send( {"from": from } ) + .then( + function(a){ + console.log("Update accepted.") + getlist(); + }, + function(b){ + fatal("\nMaybe you are not authorized:\n"+b+"\n\n\nI think you should leave now.\n"); + } + ) + ; + } + else + fatal( "I have no idea what to do with \""+answer+"\"." ); + rl.close; + }); +} + +function displayBalances( well ) +{ + var n = well.length; + var i; + if ( well == undefined ) + fatal( "Bank is not defined." ); + well.sort(wellSort); + console.log("The contract's account ("+contractaddr+") has "+Math.floor(contractbalance/1000000)+" Mwei.\n"); + var longestbefore = 1; + var longestafter = 9; + for ( i=0; i<n; i++ ) + { + var len = Math.floor(well[i][1]/1000000).toString().length; + if ( len > longestbefore ) + longestbefore = len; + } + for ( i=0; i<n; i++ ) + { + var entry = well[i]; + if ( entry == undefined ) + console.log( i+": <undef>" ); + else + { + var numstr = ""+well[i][1]/1000000; + var before = numstr; + var after = ""; + if ( numstr.search(/\./) > -1 ) + { + before = numstr.replace(/\.[0-9]+$/, ''); + after = numstr.replace(/^[0-9]*\./, ''); + } + while ( before.length < longestbefore ) + before = " "+before; + while ( after.length < longestafter ) + after += "0"; + numstr = before+"."+after; + var has = well[i][2].toString(); + if ( has.length > 9 ) + has = has.replace(/[0-9]{9}$/, ".$&"); + console.log( i+": "+well[i][0]+" fills to "+numstr+" Mwei (has "+has+")." ); + } + } + console.log("\n[ Q=quit x=distribute ]"); + rl.question("Which account to edit (enter index number of full account number)?: ", + (answer) => { + if ( answer != undefined ) + { + if ( (""+answer).toUpperCase() == 'Q' ) + process.exit( 0 ); + else + editAccount(answer, well); + } + rl.close; + } + ) +} + +function getlist() +{ + GasWell.methods.numberOfBeneficiaries() + .call() + .then( + requestBalances, + function beneficiaryFail(x){console.log(x)} + ); +} + +init(); diff --git a/bin/GasAdmin.sh b/bin/GasAdmin.sh new file mode 100755 index 0000000000000000000000000000000000000000..bf24c32a84cd632aae916f4dacb4a210103e63a5 --- /dev/null +++ b/bin/GasAdmin.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi +source ${BFAHOME}/bin/libbfa.sh || exit 1 + +# get number of accounts +# get limit of every account +# allow scroll up/down to select an account +# press enter to edit an account's limit +# press ins to add an account +# press del to delete an account (set limit = 0) +# press Q to quit. + +js=$( mktemp ) +out=$( mktemp ) +cleanup $js $out +yes | head -1000 + +function admin +{ + while : + do + ( + echo var contract = $( contract GasWell ) + echo "var pos = contract.numberOfBeneficiaries.call();" + echo "while (pos-- > 0) { var r=contract.atPosition(pos); console.log(r[0]+':'+r[1]);}" + ) > $js +cat $js + geth_attach < $js > $out +cat $out + readarray -t < $out + # Delete last entry, because that's the "return value from our call to geth". + unset MAPFILE[$(( ${#MAPFILE[*]} - 1 ))] + if [ ${#MAPFILE[*]} -gt 0 ] + then + printf "%5s %-42s %10s\n" Index "Account address" Value + echo '' + for key in ${!MAPFILE[*]} + do + val=${MAPFILE[$key]} + printf "%5d %-42s %10s\n" $key ${val%:*} ${val#*:} + done + echo + echo '["+"=New entry, Q=quit, index number]' + read -p "Change which one of these: " + else + echo "No accounts found." + REPLY="+" + fi + if [ "${REPLY^^}" = "Q" -o -z "${REPLY}" ] + then + return + elif [ "${REPLY}" = "+" ] + then + read -p "Enter new account address: " acct + if [ ${#acct} -eq 40 -a "${acct:0:2}" != "0x" ] + then + acct="0x${acct}" + fi + amount=0 + elif [ "${REPLY}" != '+' ]; REPLY=$( echo ${REPLY} | sed 's/[^0-9]//' ); [ -n "${REPLY}" ] + then + if [ ${#MAPFILE[*]} -gt "${REPLY}" ] + then + amount=${MAPFILE[${REPLY}]} + acct=${amount%:*} + amount=${amount#*:} + fi + else + return + fi + echo '["DEL"=delete]' + read -i "${amount}" -p "Set value for account ${acct}: " amount + if [ "${amount^^:0:3}" = "DEL" ] + then + amount=0 + fi + contractSendTx GasWell setEtherAllowance \"$acct\" ${amount} | geth_attach + done +} + +admin + +#void setEtherAllowance( address, uint256 ) +#uint256 numberOfBeneficiaries( ) +#void distribute( ) +#uint256 getEtherAllowance( address ) diff --git a/bin/bfalog.sh b/bin/bfalog.sh new file mode 100755 index 0000000000000000000000000000000000000000..69701e18e3be634005fd28e595aaaed6d058161c --- /dev/null +++ b/bin/bfalog.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Robert Martin-Legene <robert@nic.ar> + +if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi +source ${BFAHOME}/bin/libbfa.sh || exit 1 + +bfaconfig node +exec tail -n 100 -F ${BFANODEDIR}/log diff --git a/bin/selfDestructGasWell.sh b/bin/selfDestructGasWell.sh new file mode 100755 index 0000000000000000000000000000000000000000..462305e8af671dfe0ff64e0318ca63265417a61c --- /dev/null +++ b/bin/selfDestructGasWell.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi +source ${BFAHOME}/bin/libbfa.sh || exit 1 + +bfaconfig network +contract="${BFANETWORKDIR}/contracts/GasWell" +realdir=$( realpath "${contract}" ) +test -r "${realdir}" +address=$( basename "${realdir}" ) +test -n "${address}" + +contractSendTx GasWell selfDestruct | tee /dev/tty | geth_attach diff --git a/bin/triggerGasWell.sh b/bin/triggerGasWell.sh new file mode 100755 index 0000000000000000000000000000000000000000..a05234d90681d2a9e0712bbd5e44eb0fc646c213 --- /dev/null +++ b/bin/triggerGasWell.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi +source ${BFAHOME}/bin/libbfa.sh || exit 1 + +bfaconfig network +contract="${BFANETWORKDIR}/contracts/GasWell" +realdir=$( realpath "${contract}" ) +test -r "${realdir}" +address=$( basename "${realdir}" ) +test -n "${address}" + +contractSendTx GasWell distribute | tee /dev/tty | geth_attach diff --git a/src/contract.GasWell.solc b/src/contract.GasWell.solc new file mode 100644 index 0000000000000000000000000000000000000000..0b7ca9d984162e9cc3d551761994fec12c972266 --- /dev/null +++ b/src/contract.GasWell.solc @@ -0,0 +1,114 @@ +// vim:filetype=javascript +pragma solidity ^0.4.24; + +contract GasWell { + address owner; + struct Allowances { + address beneficiary; + uint256 value; + } + Allowances[] thelist; + // We use distpos to remember where we were stopped processing last time + // we were called. The idea is, that if we have too many accounts to take + // care of, and too little gasleft, then we stop before we run out of + // gas, since that would undo all the transactions we had already handled. + // Also, we don't want to favour anyone in particular in the list, such + // that some would have first priority in getting ether at every invocation + // So we continue checking the list from the same place where we left off + // at the previous invocation of distribute() + uint256 distpos; + + constructor() public payable { + owner = msg.sender; + } + modifier onlyOwner { + require( msg.sender == owner ); + _; + } + // Using this function, you can find out how long thelist is. + function numberOfBeneficiaries() public view returns (uint256) { + return thelist.length; + } + // Using this function, you get the address and "value" at a given position in thelist. + function atPosition( uint256 idx ) public view returns (address,uint256) { + require( idx <= thelist.length, "There are not that many addresses in the list." ); + return (thelist[idx].beneficiary,thelist[idx].value); + } + // Returns a (signed) position of where an address can be found in thelist. + // Or returns -1 if the address is not found in thelist. + function _beneficiaryPosition( address beneficiary ) internal view returns (int256) { + uint256 pos = thelist.length & 0x7fffffffffffffffffffffffffffffff; + while ( pos-- > 0 ) + if ( beneficiary == thelist[pos].beneficiary ) + return int256( pos & 0x7fffffffffffffffffffffffffffffff ); + return -1; + } + // This function returns the "allowance" that a given address is set to. + // Using this function, you don't have to cycle through atPosition() until + // you find the address you want to know about. + function getEtherAllowance( address beneficiary ) public view returns (uint256) { + int256 pos = _beneficiaryPosition( beneficiary ); + if ( pos == -1 ) + return 0; + return thelist[uint256(pos)].value; + } + // This admin (ownerOnly) function allows the creator of the contract to + // add/change/delete "allowances" per address. + function setEtherAllowance( address beneficiary, uint256 value ) public onlyOwner { + int256 pos = _beneficiaryPosition( beneficiary ); + if ( value > 0 ) { + if ( pos == -1 ) + // Add the address to thelist if it was not already there + thelist.push( Allowances(beneficiary, value) ); + else + // Simple update the value of this address + thelist[uint256(pos)].value = value; + return; + } else { + // The beneficiary is set to have 0 Ether, so we + // delete the beneficiary from the list + uint i = uint256(pos) + 1; + while ( i++ < thelist.length ) + thelist[i] = thelist[i+1]; + // If distpos was past the position that we removed, + // then move that back one. + if ( distpos > uint256(pos) ) + distpos--; + // Shorten the list + thelist.length--; + } + } + function selfDestruct() public onlyOwner { + selfdestruct( owner ); + } + function distribute() external { + uint256 listlength = thelist.length; + // Is there anything to do at all + if ( listlength == 0 ) + return; + // Has the list gotten shorter since we we were last time? + // This shouldn't happen, but it's better to be safe than to be sorry. + if ( distpos >= listlength ) + distpos = 0; + uint256 wheretostop = distpos; + while ( gasleft() > 54321 ) { + // Did we get to the end of the list, then start over + if ( ++distpos >= listlength ) + distpos = 0; + uint256 balance = thelist[distpos].beneficiary.balance; + uint256 value = thelist[distpos].value; + if ( balance < value && (value+diff) > 0 ) + { + uint256 diff = value - balance; + // we use send() instead of transfer(), because + // transfer() can throw(), and we don't want + // to stop processing because of a single error. + thelist[distpos].beneficiary.send( diff ) || true; + } + if ( wheretostop == distpos ) + return; + } + } + function () external payable { + } +} 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 ); }