diff --git a/README.md b/README.md index 2af6ae5..877aaee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,67 @@ # lister Simple Solidity contract to maintain lists. + +** Work in Progress ** + +# Contract +This contract is an implementation of the Average Salary game as described here. + +## Abstract +The following are the steps to be followed to interact with this contract: + +1. Create an empty list. +2. Add an arbitrary number of persons to this list. +3. Every member inputs a number that adds to a running total. +4. After all members are added, an average over this running total is calculated. + +# Methods +The following is a brief description of the methods that can be invoked by a `web3.js` client. + +**`constructor`** + +**`buildList(bytes32[] members)`** + +**`addNumber(bytes32 member, uint256 number)`** + +**`getRunningNumber()`** + +**`getAverageNumber()`** + +# Usage +This implementation was developed with `ethereum-testrpc` deployed on an AWS EC2 instance. Therefore, the `web3.js` client uses that IP address. One can also use `http://localhost:8545` for a local installation. Start the installation with `node_modules/.bin/testrpc` from the installation location of `ethereum-testrpc`. + +## Create a new list +After setting the IP address in `deploy-contract.js`, run `node deploy-contract.js` on command line. The output will be the address (e.g. `0x2a30f190a7d5abd6070d9ed5ec06fb4ab410bcc0`) where this contract was saved on blockchain. We now have an empty list whose owner is `A`. + +## Build the list, add numbers and calculate average +In `test-contract-0.js` set the IP address of the running `ethereum-testrpc` installation and use the contract address generated in previous step. Run `node test-contract-0.js` to submit seven transactions that include +* Addition of members to the list created above +* Adding numbers from each member +* Calculation of average. + +The output will be six transaction hashes followed by one line for the console output of running total accumulated so far. The last line is the transaction hash of average calculation. + +For example, +``` +0xb61c8d24d3e69b2a8173c72eaacfe6ec46b565c76e42659f5cb2dcacd39c4c6a +0xc08f67e266892213a1ff92f58bcde2c8e8c473557c24d3d86b434a22000d2680 +0xf3f132f8424fd8d70fd390f19c5d2b032725ecb0f12dea8df154abed9f7766c9 +0x949daf483c0364bbb10ea40a42127a2c01f51ad52b65b263a86791b20a6b7eda +0xeb229bdd5a250cfcfb9e11a9d1333d2f3d2273392b552669ed64a0361134bfba +0x8219d3d50a5537a847e7b63f81eda1fee8784b8706f32624d8bf9d88000eb988 +{ [String: '19000'] s: 1, e: 4, c: [ 19000 ] } +0x86c32c22d690a6f7af61b427d408b770a3baa6255cd42a33df22ce09ab72349c +``` + +## Get average +To get average, run `node test-contract-1.js` with updated IP address and contract address. Since this is a method `call` and not a `sendTransaction`, there is only console log and no transaction output. For example, +``` +{ [String: '3600'] s: 1, e: 3, c: [ 3600 ] } +``` + +# Known bugs + +1. Random number is hard-coded. Using randao is an option. +2. Any list member can initiate addition of numbers. The addition should be initated by list owner. +3. Calculation of average happens if all members have not provided their numbers for addition. +4. Running total is saved on the blockchain and therefore, publicly visible. diff --git a/bin/sol/Lister.abi b/bin/sol/Lister.abi new file mode 100644 index 0000000..9869a03 --- /dev/null +++ b/bin/sol/Lister.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"getAverageNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"allMembersAdded","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"member","type":"bytes32"}],"name":"memberIsOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"calculateAverageNumber","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"member","type":"bytes32"},{"name":"number","type":"uint256"}],"name":"addNumber","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"listMembers","type":"bytes32[]"}],"name":"buildList","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"lm","outputs":[{"name":"listOwner","type":"bytes32"},{"name":"randomNumber","type":"uint256"},{"name":"averageNumber","type":"uint256"},{"name":"runningNumber","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getRunningNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"addedMembers","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"member","type":"bytes32"}],"name":"memberAdded","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"inputs":[{"name":"listOwner","type":"bytes32"}],"payable":false,"type":"constructor"}] \ No newline at end of file diff --git a/bin/sol/Lister.bin b/bin/sol/Lister.bin new file mode 100644 index 0000000..6b04ad5 --- /dev/null +++ b/bin/sol/Lister.bin @@ -0,0 +1 @@ +6060604052341561000f57600080fd5b60405160208061055d833981016040528080519150505b600081905560016100378180610071565b5060018054829190600090811061004a57fe5b906000526020600020900160005b50556103e8600255600060038190556004555b506100bc565b8154818355818115116100955760008381526020902061009591810190830161009b565b5b505050565b6100b991905b808211156100b557600081556001016100a1565b5090565b90565b610492806100cb6000396000f300606060405236156100a15763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166301e25d2d81146100a65780630dec01b9146100cb5780633119bfc7146100f25780635b7657e71461011c578063633a83f91461013157806366590fb91461014c578063803ba97e1461019d5780638eef9182146101dc578063a5aef7f614610201578063caaa0ceb1461022b575b600080fd5b34156100b157600080fd5b6100b9610255565b60405190815260200160405180910390f35b34156100d657600080fd5b6100de61025c565b604051901515815260200160405180910390f35b34156100fd57600080fd5b6100de6004356102c1565b604051901515815260200160405180910390f35b341561012757600080fd5b61012f6102e5565b005b341561013c57600080fd5b61012f60043560243561030a565b005b341561015757600080fd5b61012f600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061037195505050505050565b005b34156101a857600080fd5b6101b06103d8565b604051938452602084019290925260408084019190915260608301919091526080909101905180910390f35b34156101e757600080fd5b6100b96103e7565b60405190815260200160405180910390f35b341561020c57600080fd5b6100de6004356103ee565b604051901515815260200160405180910390f35b341561023657600080fd5b6100de600435610403565b604051901515815260200160405180910390f35b6003545b90565b60006001815b6001548110156102b8578180156102ad5750600180546005916000918490811061028857fe5b906000526020600020900160005b5054815260208101919091526040016000205460ff165b91505b600101610262565b8192505b505090565b600080546001908314156102d7575060016102db565b5060005b8091505b50919050565b60015460025460045403600082828115156102fc57fe5b04600381905590505b505050565b61031382610403565b1515600114156103225761036b565b6000828152600560205260409020805460ff19166001179055610344826102c1565b1515600114156103625760025460048054918301909101905561036b565b60048054820190555b5b5b5050565b60008151600190810190610385908261041b565b50600090505b815181101561036b578181815181106103a057fe5b90602001906020020151600180548382019081106103ba57fe5b906000526020600020900160005b50555b60010161038b565b5b5050565b60005460025460035460045484565b6004545b90565b60056020526000908152604090205460ff1681565b60008181526005602052604090205460ff165b919050565b81548183558181151161030557600083815260209020610305918101908301610445565b5b505050565b61025991905b8082111561045f576000815560010161044b565b5090565b905600a165627a7a72305820e1a56695c72066ac27c60d4d4ea6dd33821d9d938ff9075959bc2ba693d30de50029 \ No newline at end of file diff --git a/bin/sol/Lister.json b/bin/sol/Lister.json new file mode 100644 index 0000000..caca550 --- /dev/null +++ b/bin/sol/Lister.json @@ -0,0 +1,36 @@ +{ + "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"getAverageNumber\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"allMembersAdded\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"member\",\"type\":\"bytes32\"}],\"name\":\"memberIsOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"calculateAverageNumber\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"member\",\"type\":\"bytes32\"},{\"name\":\"number\",\"type\":\"uint256\"}],\"name\":\"addNumber\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"listMembers\",\"type\":\"bytes32[]\"}],\"name\":\"buildList\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"lm\",\"outputs\":[{\"name\":\"listOwner\",\"type\":\"bytes32\"},{\"name\":\"randomNumber\",\"type\":\"uint256\"},{\"name\":\"averageNumber\",\"type\":\"uint256\"},{\"name\":\"runningNumber\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getRunningNumber\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"addedMembers\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"member\",\"type\":\"bytes32\"}],\"name\":\"memberAdded\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"listOwner\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"constructor\"}]", + "bytecode": "6060604052341561000f57600080fd5b60405160208061055d833981016040528080519150505b600081905560016100378180610071565b5060018054829190600090811061004a57fe5b906000526020600020900160005b50556103e8600255600060038190556004555b506100bc565b8154818355818115116100955760008381526020902061009591810190830161009b565b5b505050565b6100b991905b808211156100b557600081556001016100a1565b5090565b90565b610492806100cb6000396000f300606060405236156100a15763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166301e25d2d81146100a65780630dec01b9146100cb5780633119bfc7146100f25780635b7657e71461011c578063633a83f91461013157806366590fb91461014c578063803ba97e1461019d5780638eef9182146101dc578063a5aef7f614610201578063caaa0ceb1461022b575b600080fd5b34156100b157600080fd5b6100b9610255565b60405190815260200160405180910390f35b34156100d657600080fd5b6100de61025c565b604051901515815260200160405180910390f35b34156100fd57600080fd5b6100de6004356102c1565b604051901515815260200160405180910390f35b341561012757600080fd5b61012f6102e5565b005b341561013c57600080fd5b61012f60043560243561030a565b005b341561015757600080fd5b61012f600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061037195505050505050565b005b34156101a857600080fd5b6101b06103d8565b604051938452602084019290925260408084019190915260608301919091526080909101905180910390f35b34156101e757600080fd5b6100b96103e7565b60405190815260200160405180910390f35b341561020c57600080fd5b6100de6004356103ee565b604051901515815260200160405180910390f35b341561023657600080fd5b6100de600435610403565b604051901515815260200160405180910390f35b6003545b90565b60006001815b6001548110156102b8578180156102ad5750600180546005916000918490811061028857fe5b906000526020600020900160005b5054815260208101919091526040016000205460ff165b91505b600101610262565b8192505b505090565b600080546001908314156102d7575060016102db565b5060005b8091505b50919050565b60015460025460045403600082828115156102fc57fe5b04600381905590505b505050565b61031382610403565b1515600114156103225761036b565b6000828152600560205260409020805460ff19166001179055610344826102c1565b1515600114156103625760025460048054918301909101905561036b565b60048054820190555b5b5b5050565b60008151600190810190610385908261041b565b50600090505b815181101561036b578181815181106103a057fe5b90602001906020020151600180548382019081106103ba57fe5b906000526020600020900160005b50555b60010161038b565b5b5050565b60005460025460035460045484565b6004545b90565b60056020526000908152604090205460ff1681565b60008181526005602052604090205460ff165b919050565b81548183558181151161030557600083815260209020610305918101908301610445565b5b505050565b61025991905b8082111561045f576000815560010161044b565b5090565b905600a165627a7a72305820e1a56695c72066ac27c60d4d4ea6dd33821d9d938ff9075959bc2ba693d30de50029", + "functionHashes": { + "addNumber(bytes32,uint256)": "633a83f9", + "addedMembers(bytes32)": "a5aef7f6", + "allMembersAdded()": "0dec01b9", + "buildList(bytes32[])": "66590fb9", + "calculateAverageNumber()": "5b7657e7", + "getAverageNumber()": "01e25d2d", + "getRunningNumber()": "8eef9182", + "lm()": "803ba97e", + "memberAdded(bytes32)": "caaa0ceb", + "memberIsOwner(bytes32)": "3119bfc7" + }, + "gasEstimates": { + "creation": [ + null, + 234000 + ], + "external": { + "addNumber(bytes32,uint256)": 41625, + "addedMembers(bytes32)": 630, + "allMembersAdded()": null, + "buildList(bytes32[])": null, + "calculateAverageNumber()": 20858, + "getAverageNumber()": 371, + "getRunningNumber()": 525, + "lm()": 1192, + "memberAdded(bytes32)": 658, + "memberIsOwner(bytes32)": 490 + }, + "internal": {} + }, + "runtimeBytecode": "606060405236156100a15763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166301e25d2d81146100a65780630dec01b9146100cb5780633119bfc7146100f25780635b7657e71461011c578063633a83f91461013157806366590fb91461014c578063803ba97e1461019d5780638eef9182146101dc578063a5aef7f614610201578063caaa0ceb1461022b575b600080fd5b34156100b157600080fd5b6100b9610255565b60405190815260200160405180910390f35b34156100d657600080fd5b6100de61025c565b604051901515815260200160405180910390f35b34156100fd57600080fd5b6100de6004356102c1565b604051901515815260200160405180910390f35b341561012757600080fd5b61012f6102e5565b005b341561013c57600080fd5b61012f60043560243561030a565b005b341561015757600080fd5b61012f600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061037195505050505050565b005b34156101a857600080fd5b6101b06103d8565b604051938452602084019290925260408084019190915260608301919091526080909101905180910390f35b34156101e757600080fd5b6100b96103e7565b60405190815260200160405180910390f35b341561020c57600080fd5b6100de6004356103ee565b604051901515815260200160405180910390f35b341561023657600080fd5b6100de600435610403565b604051901515815260200160405180910390f35b6003545b90565b60006001815b6001548110156102b8578180156102ad5750600180546005916000918490811061028857fe5b906000526020600020900160005b5054815260208101919091526040016000205460ff165b91505b600101610262565b8192505b505090565b600080546001908314156102d7575060016102db565b5060005b8091505b50919050565b60015460025460045403600082828115156102fc57fe5b04600381905590505b505050565b61031382610403565b1515600114156103225761036b565b6000828152600560205260409020805460ff19166001179055610344826102c1565b1515600114156103625760025460048054918301909101905561036b565b60048054820190555b5b5b5050565b60008151600190810190610385908261041b565b50600090505b815181101561036b578181815181106103a057fe5b90602001906020020151600180548382019081106103ba57fe5b906000526020600020900160005b50555b60010161038b565b5b5050565b60005460025460035460045484565b6004545b90565b60056020526000908152604090205460ff1681565b60008181526005602052604090205460ff165b919050565b81548183558181151161030557600083815260209020610305918101908301610445565b5b505050565b61025991905b8082111561045f576000815560010161044b565b5090565b905600a165627a7a72305820e1a56695c72066ac27c60d4d4ea6dd33821d9d938ff9075959bc2ba693d30de50029" +} \ No newline at end of file diff --git a/js/deploy-contract.js b/js/deploy-contract.js new file mode 100644 index 0000000..2b29216 --- /dev/null +++ b/js/deploy-contract.js @@ -0,0 +1,22 @@ +var Web3 = require('web3'); +var fs = require('fs'); +// +var ethHttpProvider = new Web3.providers.HttpProvider("http://18.221.29.91:8545"); +var web3 = new Web3(ethHttpProvider); +var abiFile = fs.readFileSync('/Users/nsubrahm/wrkspc-sol/lister/lister/bin/sol/Lister.abi').toString(); +var abiDef = JSON.parse(abiFile); +var byteCode = fs.readFileSync('/Users/nsubrahm/wrkspc-sol/lister/lister/bin/sol/Lister.bin').toString(); +// +var listContract = web3.eth.contract(abiDef); +var deployedContract = listContract.new(['A'], + { data: byteCode, from: web3.eth.accounts[0], gas: 500000}, + (err, res) => { + if (err) { + console.log(err); + return; + } else { + console.log("Transaction hash : " + res.transactionHash); + console.log("Contract address : " + res.address); + } + } +); \ No newline at end of file diff --git a/js/test-contract-0.js b/js/test-contract-0.js new file mode 100644 index 0000000..be561ca --- /dev/null +++ b/js/test-contract-0.js @@ -0,0 +1,81 @@ +var Web3 = require('web3'); +var fs = require('fs'); +// +var web3 = new Web3(new Web3.providers.HttpProvider("http://18.221.29.91:8545")); +var abiFile = fs.readFileSync('/Users/nsubrahm/wrkspc-sol/lister/lister/bin/sol/Lister.abi').toString(); +var abiDef = JSON.parse(abiFile); +var ListingContract = web3.eth.contract(abiDef); +var contractInstance = ListingContract.at('0x2a30f190a7d5abd6070d9ed5ec06fb4ab410bcc0'); +// +contractInstance.buildList(['B', 'C', 'D', 'E'], { from: web3.eth.accounts[0], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.addNumber(['A'], [2000], { from: web3.eth.accounts[0], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.addNumber(['B'], [2000], { from: web3.eth.accounts[1], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.addNumber(['C'], [3000], { from: web3.eth.accounts[2], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.addNumber(['D'], [5000], { from: web3.eth.accounts[3], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.addNumber(['E'], [6000], { from: web3.eth.accounts[4], gas: 470000 }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log(res); + contractInstance.getRunningNumber.call( + (err, res) => { + if (err) { + console.log(err) + } else { + console.log(res) + contractInstance.calculateAverageNumber({ from: web3.eth.accounts[0], gas: 55000 }, + (err, res) => { + if (err) { + console.log(err) + } else { + console.log(res) + } + } + ) + } + } + ) + } + } + ) + } + } + ) + } + } + ) + } + } + ) + } + } + ) + } + } +) \ No newline at end of file diff --git a/js/test-contract-1.js b/js/test-contract-1.js new file mode 100644 index 0000000..6cfe8b0 --- /dev/null +++ b/js/test-contract-1.js @@ -0,0 +1,18 @@ +var Web3 = require('web3'); +var fs = require('fs'); +// +var web3 = new Web3(new Web3.providers.HttpProvider("http://18.221.29.91:8545")); +var abiFile = fs.readFileSync('/Users/nsubrahm/wrkspc-sol/lister/lister/bin/sol/Lister.abi').toString(); +var abiDef = JSON.parse(abiFile); +var ListingContract = web3.eth.contract(abiDef); +var contractInstance = ListingContract.at('0x2a30f190a7d5abd6070d9ed5ec06fb4ab410bcc0'); +// +contractInstance.getAverageNumber.call( + (err, res) => { + if (err) { + console.log(err) + } else { + console.log(res) + } + } +) \ No newline at end of file diff --git a/sol/lister.sol b/sol/lister.sol new file mode 100644 index 0000000..ba0ca7c --- /dev/null +++ b/sol/lister.sol @@ -0,0 +1,102 @@ +pragma solidity ^0.4.11; +// @title lister - calculate average with zero knowledge of numbers +// +// Begin contract defintion +contract Lister { + + // A list owned by an individual and containing multiple numbers. + // List is updated by adding number pushed by members and a random number generated for list owner only. + struct ListMembers { + bytes32 listOwner; + bytes32[] members; + uint256 randomNumber; + uint256 averageNumber; + uint256 runningNumber; + } + + // Instance of the ListMemebers struct + ListMembers public lm; + mapping (bytes32 => bool) public addedMembers; + + // Constructor to initialise the ListMembers struct + function Lister(bytes32 listOwner) { + lm.listOwner = listOwner; + lm.members.length = 1; + lm.members[0] = listOwner; + lm.randomNumber = 1000; + lm.averageNumber = 0; + lm.runningNumber = 0; + } + + // Add members to the ListMembers struct + function buildList(bytes32[] listMembers) { + lm.members.length = listMembers.length + 1; // Will this overwrite the constructor settings? Hence, the next line. + for (uint i = 0; i < listMembers.length; i++) { + lm.members[i+1] = listMembers[i]; + } + } + + // Add number provided by a member. If member is owner, add random number also. + function addNumber(bytes32 member, uint256 number) { + if (memberAdded(member) == true) { + return; + } else { + addedMembers[member] = true; + if (memberIsOwner(member) == true) { + lm.runningNumber += lm.randomNumber + number; + } else { + lm.runningNumber += number; + } + } + } + + // Check if member has already added a number + function memberAdded(bytes32 member) returns (bool) { + return addedMembers[member]; + } + + // Check if member is the owner of the list + function memberIsOwner(bytes32 member) returns (bool) { + bool owner = true; + + if (lm.listOwner==member) { + owner = true; + } else { + owner = false; + } + return owner; + } + + // Calculate average of all numbers in the list only if all members have been added to the list. + function calculateAverageNumber() { + /** + if (allMembersAdded() == false) { + return ; + } + */ + uint256 numMembers = uint256(lm.members.length); + uint256 sampleSum = lm.runningNumber - lm.randomNumber; + uint256 avgSum = sampleSum / numMembers; + lm.averageNumber = avgSum; + } + + // Check if all members are added. Average will be calculated only if all members are added. + function allMembersAdded() returns (bool) { + bool added = true; + for (uint i = 0; i < lm.members.length; i++) { + added = added && addedMembers[lm.members[i]]; + } + + return added; + } + + // Get the running sum of all numbers. This function will be omitted from the final version. + function getRunningNumber() constant returns (uint256) { + return lm.runningNumber; + } + + // Get the average of all numbers in this list. + function getAverageNumber() constant returns (uint256) { + return lm.averageNumber; + } +} \ No newline at end of file