From a1dd3763824c7635a0239e535cb8fb9b0585bb02 Mon Sep 17 00:00:00 2001 From: Robert Martin-Legene <robert@nic.ar> Date: Thu, 23 Aug 2018 20:45:04 -0300 Subject: [PATCH] Introducing the Gas Well. Distribution still does not work. --- README.md | 7 + bin/GasAdmin.js | 294 +++++++++++++++++++++++++++++++++++++ bin/GasAdmin.sh | 87 +++++++++++ bin/selfDestructGasWell.sh | 13 ++ bin/triggerGasWell.sh | 13 ++ src/contract.GasWell.solc | 114 ++++++++++++++ 6 files changed, 528 insertions(+) create mode 100755 bin/GasAdmin.js create mode 100755 bin/GasAdmin.sh create mode 100755 bin/selfDestructGasWell.sh create mode 100755 bin/triggerGasWell.sh create mode 100644 src/contract.GasWell.solc diff --git a/README.md b/README.md index 309c191..41eb652 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 0000000..3cf94f4 --- /dev/null +++ b/bin/GasAdmin.js @@ -0,0 +1,294 @@ +#!/usr/bin/node + +var port = 14349; // cloud +port = 16437; // bc +const contractaddr= '0x283bc55557c08dbc4f902b2b8479a2201a47e1c1'; +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; + +function init() +{ + if ( process.env.BFAHOME == undefined ) + { + console.log( "$BFAHOME not set. Did you source bfa/bin/env ?" ); + process.exit( 1 ); + } + web3 = new Web3( "http://127.0.0.1:"+port ); + //accounts = new Accounts( web3 ); + var abi = ""; + var abifile = fs.createReadStream( + process.env.BFAHOME+"/network5445/contracts/"+contractaddr+"/abi" ) + .on('readable', () => { + var data; + while (data = abifile.read()) + abi += data; + }) + .on('end', () => { + GasWell = new web3.eth.Contract( JSON.parse(abi), contractaddr ); + 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){ + console.log("Did you remember to allow 'personal' via the RPC port? " + err) + process.exit( 1 ); + } + ); +} + +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 + { + console.log("I don't know what to do with \""+entry+"\"." ); + process.exit( 1 ); + } + 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){ + console.log("\nMaybe you are not authorized:\n"+b+"\n\n\nI think you should leave now.\n"); + process.exit( 1 ); + } + ) + ; + } + else + { + console.log( "I have no idea what to do with \""+answer+"\"." ); + process.exit( 1 ); + } + rl.close; + }); +} + +function displayBalances( well ) +{ + var n = well.length; + var i; + if ( well == undefined ) + { + console.log( "Bank is not defined." ); + process.exit( 1 ); + } + well.sort(wellSort); + console.log(""); + 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(""); + 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 0000000..bf24c32 --- /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/selfDestructGasWell.sh b/bin/selfDestructGasWell.sh new file mode 100755 index 0000000..462305e --- /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 0000000..a05234d --- /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 0000000..0b7ca9d --- /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 { + } +} -- GitLab