├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── contracts ├── Migrations.sol └── ProofOfProduceQuality.sol ├── package.json ├── utils.js ├── .vscode └── launch.json ├── LICENSE ├── .gitignore ├── test └── proofOfProduceQuality.js ├── deploy.js └── README.md /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ProofOfProduceQuality = artifacts.require("./ProofOfProduceQuality.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(ProofOfProduceQuality); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | }, 9 | rpc: { 10 | gas : 4712388 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supply-chain-smart-contracts", 3 | "version": "1.0.0", 4 | "description": "Supply Chain smart contract", 5 | "main": "deploy.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/CatalystCode/supply-chain-smart-contracts.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/CatalystCode/supply-chain-smart-contracts/issues" 20 | }, 21 | "homepage": "https://github.com/CatalystCode/supply-chain-smart-contracts#readme", 22 | "dependencies": { 23 | "solc": "^0.4.15", 24 | "web3": "^0.20.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | 2 | var util = require('util'); 3 | 4 | async function callAsyncFunc(obj, func) { 5 | return new Promise((resolve, reject) => { 6 | 7 | // callback for the function invoked 8 | var cb = (err, result) => { 9 | if (err) { 10 | console.error(`error executing function '${func}' with params: ${util.inspect(arguments)}: ${err.message}`); 11 | return reject(err); 12 | } 13 | 14 | console.log(`function '${func}' completed successfully with result: ${util.inspect(result)}`); 15 | return resolve({ result }); 16 | }; 17 | 18 | var params = Array.prototype.slice.call(arguments, 2); 19 | params.push(cb); 20 | 21 | return obj[func].apply(obj, params); 22 | }); 23 | } 24 | 25 | async function sleep(timeSecs) { 26 | return new Promise(resolve => setTimeout(() => resolve(), timeSecs * 1000)); 27 | } 28 | 29 | module.exports = { 30 | callAsyncFunc, 31 | sleep 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}\\deploy.js", 12 | "args": [ 13 | "ProofOfProduceQuality", "http://104.40.181.78:8545", "AUser1234567" 14 | ], 15 | 16 | "args_local": [ 17 | "ProofOfProduceQuality", "http://localhost:8545", "Pa$$word1" 18 | ], 19 | "args_preview": [ 20 | "ProofOfProduceQuality", "http://104.40.181.78:8545", "AUser1234567" 21 | ], 22 | "args_prod": [ 23 | "ProofOfProduceQuality", "http://ibr10lqwj.westeurope.cloudapp.azure.com:8545", "Password12345" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Beat Schwegler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # remove private configuration 3 | *.private.json 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | node_modules 65 | -------------------------------------------------------------------------------- /test/proofOfProduceQuality.js: -------------------------------------------------------------------------------- 1 | 2 | var ProofOfProduceQuality = artifacts.require("../contracts/ProofOfProduceQuality.sol"); 3 | 4 | contract('ProofOfProduceQuality', function(accounts) { 5 | it("stores proofs and retreives them", function() { 6 | 7 | var trackingId0 = "root"; 8 | var trackingId1 = "trackingId1"; 9 | var trackingId2 = "trackingId2"; 10 | 11 | var proofValue1 = "publicProof1"; 12 | var proofValue2 = "publicProof2"; 13 | 14 | return ProofOfProduceQuality.deployed().then(function(instance) { 15 | return instance.storeProof(trackingId1,trackingId0,proofValue1,proofValue1).then(function(value){ 16 | assert(value); 17 | 18 | instance.storeProof(trackingId2,trackingId1,proofValue2,proofValue2).then(function(value){ 19 | assert(value); 20 | 21 | instance.getProof(trackingId2).then(function(proof2){ 22 | console.log('proof2=' + JSON.stringify(proof2)); 23 | assert.equal(proof2[1],proofValue2); 24 | assert.equal(proof2[3],trackingId1); 25 | }); 26 | }); 27 | 28 | instance.getProof(trackingId1).then(function(proof1){ 29 | console.log('proof1=' + JSON.stringify(proof1)); 30 | assert.equal(proof1[1],proofValue1); 31 | assert.equal(proof1[3],trackingId0); 32 | }); 33 | } 34 | ); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /deploy.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | var util = require('util'); 4 | const solc = require('solc'); 5 | const Web3 = require('web3'); 6 | 7 | var utils = require('./utils'); 8 | var callAsyncFunc = utils.callAsyncFunc; 9 | 10 | const SUCCESS_EXIT_CODE = 0; 11 | const MISSING_ARGUMENT_EXIT_CODE = 1; 12 | const GENERAL_ERROR_EXIT_CODE = 2; 13 | 14 | var deployContract = async (opts) => { 15 | try { 16 | 17 | var contractName = opts.contractName; 18 | var rpcEndpoint = opts.rpcEndpoint; 19 | var accountPassword = opts.accountPassword; 20 | 21 | console.log(`deploying contract on ${rpcEndpoint}`); 22 | const web3 = new Web3(new Web3.providers.HttpProvider(rpcEndpoint)); 23 | 24 | // compile contract 25 | const input = fs.readFileSync(`contracts/${contractName}.sol`); 26 | const output = solc.compile(input.toString(), 1); 27 | var compiledContract = output.contracts[`:${contractName}`]; 28 | const bytecode = '0x' + compiledContract.bytecode; 29 | const abi = JSON.parse(compiledContract.interface); 30 | const contract = web3.eth.contract(abi); 31 | 32 | // get coinbase address 33 | var getCoinbaseRequest = await callAsyncFunc(web3.eth, 'getCoinbase'); 34 | var accountAddress = getCoinbaseRequest.result; 35 | 36 | // unlock the account 37 | var unlockRes = await callAsyncFunc(web3.personal, 'unlockAccount', accountAddress, accountPassword); 38 | if (!unlockRes.result) { 39 | throw new Error(`error unlocking account: ${accountAddress}`); 40 | } 41 | 42 | // deploy contract 43 | var deployResult = contract.new(accountAddress, { 44 | from: accountAddress, 45 | password: accountPassword, 46 | data: bytecode, 47 | gas: 2000000 }); 48 | 49 | var txHash = deployResult.transactionHash; 50 | 51 | // lock the account 52 | var lockRes = await callAsyncFunc(web3.personal, 'lockAccount', accountAddress, accountPassword); 53 | if (!lockRes) { 54 | throw new Error(`error locking account: ${opts.config.from}`); 55 | } 56 | 57 | // wait until the contract is mined 58 | var receipt; 59 | while (!receipt) 60 | { 61 | console.log(`waiting for contract to be mined...`); 62 | await utils.sleep(5); 63 | var receiptRequest = await callAsyncFunc(web3.eth, 'getTransactionReceipt', txHash); 64 | receipt = receiptRequest.result; 65 | } 66 | 67 | // get contract address 68 | var contractAddress = receipt.contractAddress; 69 | console.log(`contract deployed on address: ${contractAddress}`); 70 | 71 | // success exit and send result to output 72 | return exit(SUCCESS_EXIT_CODE, { 73 | accountAddress, 74 | contractAddress 75 | }); 76 | 77 | } 78 | catch(err) { 79 | return exit(GENERAL_ERROR_EXIT_CODE, { 80 | error: `error deploying contract: ${err.message}` 81 | }); 82 | } 83 | } 84 | 85 | function exit(exitCode, obj) { 86 | var logLevel = exitCode ? 'error' : 'info'; 87 | console[logLevel](JSON.stringify(obj)); 88 | process.nextTick(() => process.exit(exitCode)); 89 | } 90 | 91 | var contractName = process.argv[2]; 92 | var rpcEndpoint = process.argv[3]; 93 | var accountPassword = process.argv[4]; 94 | if (!contractName || !rpcEndpoint || !accountPassword) { 95 | return exit(MISSING_ARGUMENT_EXIT_CODE, { 96 | error: `missing arguments, command line usage: node deploy.js ` 97 | }); 98 | } 99 | 100 | deployContract({ 101 | contractName, 102 | rpcEndpoint, 103 | accountPassword 104 | }); 105 | 106 | -------------------------------------------------------------------------------- /contracts/ProofOfProduceQuality.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract ProofOfProduceQuality { 4 | struct ProofEntry { 5 | address owner; 6 | string encryptedProof; 7 | string publicProof; 8 | string previousTrackingId; 9 | } 10 | 11 | // map of trackingId to proofEntry 12 | mapping (string => ProofEntry) private proofs; 13 | 14 | // map of trackingId to addresses to check if a trackingId can be use as a previousTrackingId 15 | mapping (string => mapping (address => bool )) private isTransfered; 16 | 17 | 18 | event StoreProofCompleted( 19 | address from, 20 | string trackingId, 21 | string previousTrackingId 22 | ); 23 | 24 | event TransferCompleted( 25 | address from, 26 | address to, 27 | string trackingId 28 | ); 29 | 30 | 31 | function ProofOfProduceQuality() { 32 | 33 | } 34 | 35 | // add a proof to an existing tracking - requires that 36 | // the previousOwner transfered the ownership 37 | function storeProof(string trackingId, string previousTrackingId, string encryptedProof, string publicProof) returns(bool success) { 38 | 39 | // if we don't already have this trackingId 40 | if (hasProof(trackingId)) { 41 | // already exists- return 42 | return false; 43 | } 44 | 45 | // if previous tracking Id was provided 46 | if (sha3(previousTrackingId) != sha3("root")) { 47 | 48 | // if the caller is not the owner of the previousId and 49 | // he didn't transfer it to the caller, return 50 | // this will terminate the tx if the previous tracking id doesn't exist 51 | ProofEntry memory pe = getProofInternal(previousTrackingId); 52 | if (msg.sender != pe.owner && !isTransfered[previousTrackingId][msg.sender]) { 53 | // no rights to use previousTrackingId. Owner need to transfer the trackingId first 54 | return false; 55 | } 56 | 57 | } 58 | 59 | proofs[trackingId] = ProofEntry(msg.sender, encryptedProof, publicProof, previousTrackingId); 60 | StoreProofCompleted(msg.sender, trackingId, previousTrackingId); 61 | return true; 62 | } 63 | 64 | function transfer(string trackingId, address newOwner) returns(bool success) { 65 | 66 | if (hasProof(trackingId)) { 67 | ProofEntry memory pe = getProofInternal(trackingId); 68 | if (msg.sender == pe.owner) { 69 | 70 | // TODO: ask Beat- why not just change the owner in the ProofEntry? why do we need the isTransfered mapping? 71 | // in this case, there might be multiple owners. Is this what we want? 72 | isTransfered[trackingId][newOwner] = true; 73 | TransferCompleted(msg.sender, newOwner, trackingId); 74 | } 75 | 76 | // TODO: ask Beat- why do we want to return true if the tx sender is not the owner? 77 | // we didn't really transfer the ownership in this case... 78 | return true; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | // returns true if proof is stored 85 | function hasProof(string trackingId) constant internal returns(bool exists) { 86 | return proofs[trackingId].owner != address(0); 87 | } 88 | 89 | 90 | // returns the proof 91 | function getProofInternal(string trackingId) constant internal returns(ProofEntry proof) { 92 | if (hasProof(trackingId)) { 93 | return proofs[trackingId]; 94 | } 95 | 96 | // proof doesn't exist, throw and terminate transaction 97 | throw; 98 | } 99 | 100 | function getProof(string trackingId) constant returns(address owner, string encryptedProof, string publicProof, string previousTrackingId) { 101 | if (hasProof(trackingId)) { 102 | ProofEntry memory pe = getProofInternal(trackingId); 103 | owner = pe.owner; 104 | encryptedProof = pe.encryptedProof; 105 | publicProof = pe.publicProof; 106 | previousTrackingId = pe.previousTrackingId; 107 | } 108 | } 109 | 110 | // returns the encrypted part of the proof 111 | function getEncryptedProof(string trackingId) constant returns(string encryptedProof) { 112 | if (hasProof(trackingId)) { 113 | return getProofInternal(trackingId).encryptedProof; 114 | } 115 | } 116 | 117 | // returns the public part of the proof 118 | function getPublicProof(string trackingId) constant returns(string publicProof) { 119 | if (hasProof(trackingId)) { 120 | return getProofInternal(trackingId).publicProof; 121 | } 122 | } 123 | 124 | function getOwner(string trackingId) constant returns(address owner) { 125 | if (hasProof(trackingId)) { 126 | return getProofInternal(trackingId).owner; 127 | } 128 | } 129 | 130 | function getPreviousTrackingId(string trackingId) constant returns(string previousTrackingId) { 131 | if (hasProof(trackingId)) { 132 | return getProofInternal(trackingId).previousTrackingId; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # supply-chain-smart-contracts 2 | 3 | ### install test-rpc locally 4 | install [testrpc](https://github.com/ethereumjs/testrpc) as your local geth-rpc or use the Docker image - if you run into problems building the image on Windows, simply build it in the Ubuntu subsystem on Windows. Here a [description](https://blog.jayway.com/2017/04/19/running-docker-on-bash-on-windows/) how to use the windows docker daemon from within the Linux subsystem. 5 | 6 | ### install geth locally 7 | follow these [instructions to install geth](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) 8 | 9 | ### deploy geth as Ubuntu VM on Azure 10 | simply deploy the follwoing arm template: 11 | https://github.com/Azure/azure-quickstart-templates/tree/master/go-ethereum-on-ubuntu 12 | 13 | Once the resource has been deployed, create a VPN to ensure the VM is isn't publicly accessible. Add a the geth-rpc port 8545 as an inbound security rule to the Nnewtwork security group (in addition to ssh). Also allow outbound traffic on all ports (or the geth peer network port only (which is ????)) to ensure that geth can connect to it's peers. 14 | 15 | To create the VPN, it might be best to first deploy the [services](https://github.com/cloudbeatsch/supply-chain-services) and create the VPN including the gateway through the WebApp's Networking settings. This will allow to create a VPN which includes the vpn gateway. 16 | 17 | ## Dependencies 18 | 19 | ### truffle 20 | install [truffle](https://github.com/trufflesuite/truffle) 21 | 22 | ``` 23 | sudo apt-get update && sudo apt-get -y upgrade 24 | sudo apt-get -y install curl git vim build-essential 25 | 26 | curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 27 | sudo apt-get install -y nodejs 28 | sudo npm install -global express 29 | 30 | sudo npm install -global truffle 31 | ``` 32 | 33 | On Windows: before you install truffle ensure you install windows-build-tools 34 | ``` 35 | npm install -global --production windows-build-tools 36 | npm install -global truffle 37 | ``` 38 | 39 | ## Test using testrpc 40 | 41 | start test-rpc: 42 | ``` 43 | docker run -d -p 8545:8545 ethereumjs/testrpc:latest -a 10 -u0 -u1 44 | ``` 45 | or in attached mode to see the transactions: 46 | ``` 47 | docker run -p 8545:8545 ethereumjs/testrpc:latest -a 10 -u0 -u1 48 | ``` 49 | 50 | ## Test in Testnet mode 51 | Ensure that the VM is accessible from a private network only. To ssh into it, a jumperBox vm can be used (a vm that acts which provides public ssh but is part of the VPN). 52 | 53 | Setup your Rinkeby Testnet by following this [article](https://gist.github.com/cryptogoth/10a98e8078cfd69f7ca892ddbdcf26bc) 54 | NOte: ensure that you start geth by enabling `rpc` and `web3`: 55 | 56 | ``` 57 | geth ... --rpc --rpcport=8545 --rpcaddr=0.0.0.0 --rpcapi="personal,admin,eth,net,web3" 58 | ``` 59 | 60 | you can check the accessability of your `geth rpc` endpoint within your private network using the following `curl` command: 61 | ``` 62 | curl -X POST --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' http://10.0.0.4:8545 63 | ``` 64 | 65 | Before we can deploy the contract, geth needs to be fully synched with he the blockchain. We can check the progress through the console: 66 | 67 | ``` 68 | > eth.syncing 69 | { 70 | currentBlock: 484548, 71 | highestBlock: 1140339, 72 | knownStates: 338331, 73 | pulledStates: 148121, 74 | startingBlock: 440322 75 | } 76 | ``` 77 | Once we're finished with syncing `eth.blockNumber` returns a the number of the latest block. While syncing, it returns `0` 78 | 79 | unlock your account: 80 | ``` 81 | personal.unlockAccount(web3.eth.coinbase, "password", 15000) 82 | ``` 83 | in your `truffle.js` add a property for your network: 84 | 85 | ``` 86 | networks: { 87 | testnet: { 88 | host: "10.0.0.4", 89 | port: 8545, 90 | network_id: "4" 91 | } 92 | } 93 | ``` 94 | 95 | deploy the contract from your jumperBox: 96 | 97 | ``` 98 | truffle migrate --reset --network testnet 99 | ``` 100 | 101 | ## Deploy the contract to the network 102 | 103 | Set the hostname in `truffle.js" to your deployed geth rpc instance - e.g. 10.0.0.4 104 | 105 | Just compile and deploy the contracts 106 | 107 | ``` 108 | truffle compile 109 | truffle migrate --reset 110 | ``` 111 | Once the block containing the contract has been added to the chain, we can test it: 112 | ``` 113 | truffle test 114 | ``` 115 | 116 | ## Use the deploy.js script 117 | Make sure you have Node.js version 7.6.0 and above. 118 | Run the following command to deploy the script to any RPC endpoint: 119 | 120 | ``` 121 | node deploy.js 122 | ``` 123 | 124 | Example: 125 | 126 | ``` 127 | node deploy ProofOfProduceQuality http://40.68.224.232:8545 MyPassword 128 | ``` 129 | 130 | In case of an error, the process will be terminated with an error exit code and the last output line will be a json containing an `error` member with the details of the error: 131 | 132 | ```json 133 | {"error":"the error details"} 134 | ``` 135 | 136 | In case of a successful execution, the process will be terminated with a success exit code (0) and the last output line will be a json containing the account address (the coinbase) and the deployed contract instance address: 137 | 138 | ```json 139 | {"accountAddress":"0x6290feb5d6155bb8ca4551bae08564afb636a974","contractAddress":"0x44D89F52f93D1bF9A0F47330B5726B0d82cD8176"} 140 | ``` 141 | 142 | --------------------------------------------------------------------------------