├── .gitignore ├── contracts ├── Main.sol ├── Migrations.sol ├── pharmaceuticalaccesscontrol │ ├── Roles.sol │ ├── ConsumerRole.sol │ ├── RetailerRole.sol │ ├── DistributorRole.sol │ └── ManufacturerRole.sol ├── pharmaceuticalcore │ └── Ownable.sol └── pharmaceuticalbase │ └── SupplyChain.sol ├── images ├── web-client.png ├── Class-Diagram.png ├── State-Diagram.png ├── truffle-test.png ├── deploy-rinkeby.png ├── Activity-Diagram.png └── Sequence-Diagram.png ├── .gitattributes ├── docs ├── Class-Diagram.pdf ├── State-Diagram.pdf ├── Activity-Diagram.pdf └── Sequence-Diagram.pdf ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── package.json ├── src ├── style.css ├── index.html └── js │ └── app.js ├── readme.md └── test └── TestSupplychain.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | .DS_Store 3 | package-lock.json 4 | /build/* 5 | .env 6 | .vscode -------------------------------------------------------------------------------- /contracts/Main.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Main { 4 | 5 | constructor () internal {} 6 | } -------------------------------------------------------------------------------- /images/web-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/web-client.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /docs/Class-Diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/docs/Class-Diagram.pdf -------------------------------------------------------------------------------- /docs/State-Diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/docs/State-Diagram.pdf -------------------------------------------------------------------------------- /images/Class-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/Class-Diagram.png -------------------------------------------------------------------------------- /images/State-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/State-Diagram.png -------------------------------------------------------------------------------- /images/truffle-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/truffle-test.png -------------------------------------------------------------------------------- /docs/Activity-Diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/docs/Activity-Diagram.pdf -------------------------------------------------------------------------------- /docs/Sequence-Diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/docs/Sequence-Diagram.pdf -------------------------------------------------------------------------------- /images/deploy-rinkeby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/deploy-rinkeby.png -------------------------------------------------------------------------------- /images/Activity-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/Activity-Diagram.png -------------------------------------------------------------------------------- /images/Sequence-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandon-zimmerman/ethereum-supply-chain/HEAD/images/Sequence-Diagram.png -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | // migrating the appropriate contracts 2 | var ManufacturerRole = artifacts.require("./ManufacturerRole.sol"); 3 | var DistributorRole = artifacts.require("./DistributorRole.sol"); 4 | var RetailerRole = artifacts.require("./RetailerRole.sol"); 5 | var ConsumerRole = artifacts.require("./ConsumerRole.sol"); 6 | var SupplyChain = artifacts.require("./SupplyChain.sol"); 7 | 8 | module.exports = function(deployer) { 9 | deployer.deploy(ManufacturerRole); 10 | deployer.deploy(DistributorRole); 11 | deployer.deploy(RetailerRole); 12 | deployer.deploy(ConsumerRole); 13 | deployer.deploy(SupplyChain); 14 | }; 15 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | var HDWalletProvider = require('truffle-hdwallet-provider'); 3 | var infuraUrl = 'https://rinkeby.infura.io/v3/' + process.env.INFURA_API_KEY; 4 | 5 | module.exports = { 6 | networks: { 7 | development: { 8 | host: "127.0.0.1", 9 | port: 8545, 10 | network_id: "*" // Match any network id 11 | }, 12 | rinkeby: { 13 | provider: function() { 14 | return new HDWalletProvider(process.env.MNEMONIC, infuraUrl) 15 | }, 16 | network_id: 4, 17 | gas: 4500000, 18 | gasPrice: 10000000000, 19 | } 20 | }, 21 | compilers: { 22 | solc: { 23 | version: "0.4.24", // Any published image name 24 | docker: false 25 | } 26 | } 27 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supply-chain-E", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "lite-server", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "pk (https://ipfs.infura.io/ipfs/QmSXiR9Khm3yo1J67nUopvVBxFJ8YGN2fUGZ34Etr3m92x/)", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "lite-server": "2.4.0" 15 | }, 16 | "dependencies": { 17 | "dotenv": "^8.0.0", 18 | "scrypt": "^6.0.3", 19 | "secp256k1": "^3.7.1", 20 | "sha3": "^2.0.6", 21 | "solc": "^0.4.24", 22 | "truffle": "^5.0.29", 23 | "truffle-blockchain-utils": "0.0.10", 24 | "truffle-hdwallet-provider": "0.0.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color:#fff; 3 | padding: 2em; 4 | font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'; 5 | color: #212121; 6 | } 7 | .container { 8 | width: 80%; 9 | margin: 0 auto; 10 | } 11 | 12 | 13 | .bold { 14 | font-weight: bolder; 15 | } 16 | 17 | input { 18 | width: 80%; 19 | padding: 12px 20px; 20 | margin: 10px 0; 21 | } 22 | 23 | button { 24 | cursor: pointer; 25 | background-color: #4CAF50; 26 | border: none; 27 | color: white; 28 | background-color: #00BCD4; 29 | font-size: 16px; 30 | padding: 16px 32px; 31 | text-decoration: none; 32 | margin: 4px 2px; 33 | cursor: pointer; 34 | } 35 | 36 | .sell { 37 | background-color: white; 38 | color: #D1C4E9; 39 | } 40 | 41 | .ftc-intro { 42 | background-color:#0097A7; 43 | color:white; 44 | width:100%; 45 | padding:20px; 46 | } 47 | 48 | .form-group { 49 | background-color:#0097A7; 50 | color:black; 51 | width:50%; 52 | padding:30px; 53 | border-radius: 4px; 54 | background-color: #f8f8f8; 55 | } 56 | 57 | .form-break { 58 | background-color:#fff; 59 | color:white; 60 | width:100%; 61 | padding:5px; 62 | } 63 | 64 | .horizontal-rule { 65 | background-color:#BDBDBD; 66 | } 67 | -------------------------------------------------------------------------------- /contracts/pharmaceuticalaccesscontrol/Roles.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title Roles 5 | * @dev Library for managing addresses assigned to a Role. 6 | */ 7 | library Roles { 8 | struct Role { 9 | mapping (address => bool) bearer; 10 | } 11 | 12 | /** 13 | * @dev give an account access to this role 14 | */ 15 | function add(Role storage role, address account) internal { 16 | require(account != address(0), "the account cannot be zero"); 17 | require(!has(role, account), "the account already has the given role"); 18 | 19 | role.bearer[account] = true; 20 | } 21 | 22 | /** 23 | * @dev remove an account's access to this role 24 | */ 25 | function remove(Role storage role, address account) internal { 26 | require(account != address(0), "the account cannot be zero"); 27 | require(has(role, account), "the account does not have the given role"); 28 | 29 | role.bearer[account] = false; 30 | } 31 | 32 | /** 33 | * @dev check if an account has this role 34 | * @return bool 35 | */ 36 | function has(Role storage role, address account) 37 | internal 38 | view 39 | returns (bool) 40 | { 41 | require(account != address(0), "the account cannot be zero"); 42 | return role.bearer[account]; 43 | } 44 | } -------------------------------------------------------------------------------- /contracts/pharmaceuticalcore/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /// Provides basic authorization control 4 | contract Ownable { 5 | address private origOwner; 6 | 7 | // Define an Event 8 | event TransferOwnership(address indexed oldOwner, address indexed newOwner); 9 | 10 | /// Assign the contract to an owner 11 | constructor () internal { 12 | origOwner = msg.sender; 13 | emit TransferOwnership(address(0), origOwner); 14 | } 15 | 16 | /// Look up the address of the owner 17 | function owner() public view returns (address) { 18 | return origOwner; 19 | } 20 | 21 | /// Define a function modifier 'onlyOwner' 22 | modifier onlyOwner() { 23 | require(isOwner(), "sender is not the owner"); 24 | _; 25 | } 26 | 27 | /// Check if the calling address is the owner of the contract 28 | function isOwner() public view returns (bool) { 29 | return msg.sender == origOwner; 30 | } 31 | 32 | /// Define a function to renounce ownerhip 33 | function renounceOwnership() public onlyOwner { 34 | emit TransferOwnership(origOwner, address(0)); 35 | origOwner = address(0); 36 | } 37 | 38 | /// Define a public function to transfer ownership 39 | function transferOwnership(address newOwner) public onlyOwner { 40 | _transferOwnership(newOwner); 41 | } 42 | 43 | /// Define an internal function to transfer ownership 44 | function _transferOwnership(address newOwner) internal { 45 | require(newOwner != address(0), "the account cannot be zero"); 46 | emit TransferOwnership(origOwner, newOwner); 47 | origOwner = newOwner; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/pharmaceuticalaccesscontrol/ConsumerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // Import the library 'Roles' 4 | import "./Roles.sol"; 5 | 6 | // Define a contract 'ConsumerRole' to manage this role - add, remove, check 7 | contract ConsumerRole { 8 | using Roles for Roles.Role; 9 | 10 | // Define 2 events, one for Adding, and other for Removing 11 | event ConsumerAdded(address indexed account); 12 | event ConsumerRemoved(address indexed account); 13 | 14 | // Define a struct 'consumers' by inheriting from 'Roles' library, struct Role 15 | Roles.Role private consumers; 16 | 17 | // In the constructor make the address that deploys this contract the 1st consumer 18 | constructor() public { 19 | _addConsumer(msg.sender); 20 | } 21 | 22 | // Define a modifier that checks to see if msg.sender has the appropriate role 23 | modifier onlyConsumer() { 24 | require(isConsumer(msg.sender), "sender is not a cosnumer"); 25 | _; 26 | } 27 | 28 | // Define a function 'isConsumer' to check this role 29 | function isConsumer(address account) public view returns (bool) { 30 | return consumers.has(account); 31 | } 32 | 33 | // Define a function 'addConsumer' that adds this role 34 | function addConsumer(address account) public onlyConsumer { 35 | _addConsumer(account); 36 | } 37 | 38 | // Define a function 'renounceConsumer' to renounce this role 39 | function renounceConsumer() public { 40 | _removeConsumer(msg.sender); 41 | } 42 | 43 | // Define an internal function '_addConsumer' to add this role, called by 'addConsumer' 44 | function _addConsumer(address account) internal { 45 | consumers.add(account); 46 | emit ConsumerAdded(account); 47 | } 48 | 49 | // Define an internal function '_removeConsumer' to remove this role, called by 'removeConsumer' 50 | function _removeConsumer(address account) internal { 51 | consumers.remove(account); 52 | emit ConsumerRemoved(account); 53 | } 54 | } -------------------------------------------------------------------------------- /contracts/pharmaceuticalaccesscontrol/RetailerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // Import the library 'Roles' 4 | import "./Roles.sol"; 5 | 6 | // Define a contract 'RetailerRole' to manage this role - add, remove, check 7 | contract RetailerRole { 8 | using Roles for Roles.Role; 9 | 10 | // Define 2 events, one for Adding, and other for Removing 11 | event RetailerAdded(address indexed account); 12 | event RetailerRemoved(address indexed account); 13 | 14 | // Define a struct 'retailers' by inheriting from 'Roles' library, struct Role 15 | Roles.Role private retailers; 16 | 17 | // In the constructor make the address that deploys this contract the 1st retailer 18 | constructor() public { 19 | _addRetailer(msg.sender); 20 | } 21 | 22 | // Define a modifier that checks to see if msg.sender has the appropriate role 23 | modifier onlyRetailer() { 24 | require(isRetailer(msg.sender), "sender is not a manufacturer"); 25 | _; 26 | } 27 | 28 | // Define a function 'isRetailer' to check this role 29 | function isRetailer(address account) public view returns (bool) { 30 | return retailers.has(account); 31 | } 32 | 33 | // Define a function 'addRetailer' that adds this role 34 | function addRetailer(address account) public onlyRetailer { 35 | _addRetailer(account); 36 | } 37 | 38 | // Define a function 'renounceRetailer' to renounce this role 39 | function renounceRetailer() public { 40 | _removeRetailer(msg.sender); 41 | } 42 | 43 | // Define an internal function '_addRetailer' to add this role, called by 'addRetailer' 44 | function _addRetailer(address account) internal { 45 | retailers.add(account); 46 | emit RetailerAdded(account); 47 | } 48 | 49 | // Define an internal function '_removeRetailer' to remove this role, called by 'removeRetailer' 50 | function _removeRetailer(address account) internal { 51 | retailers.remove(account); 52 | emit RetailerRemoved(account); 53 | } 54 | } -------------------------------------------------------------------------------- /contracts/pharmaceuticalaccesscontrol/DistributorRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // Import the library 'Roles' 4 | import "./Roles.sol"; 5 | 6 | // Define a contract 'DistributorRole' to manage this role - add, remove, check 7 | contract DistributorRole { 8 | using Roles for Roles.Role; 9 | 10 | // Define 2 events, one for Adding, and other for Removing 11 | event DistributorAdded(address indexed account); 12 | event DistributorRemoved(address indexed account); 13 | 14 | // Define a struct 'distributors' by inheriting from 'Roles' library, struct Role 15 | Roles.Role private distributors; 16 | 17 | // In the constructor make the address that deploys this contract the 1st distributor 18 | constructor() public { 19 | _addDistributor(msg.sender); 20 | } 21 | 22 | // Define a modifier that checks to see if msg.sender has the appropriate role 23 | modifier onlyDistributor() { 24 | require(isDistributor(msg.sender), "sender is not a distibutor"); 25 | _; 26 | } 27 | 28 | // Define a function 'isDistributor' to check this role 29 | function isDistributor(address account) public view returns (bool) { 30 | return distributors.has(account); 31 | } 32 | 33 | // Define a function 'addDistributor' that adds this role 34 | function addDistributor(address account) public onlyDistributor { 35 | _addDistributor(account); 36 | } 37 | 38 | // Define a function 'renounceDistributor' to renounce this role 39 | function renounceDistributor() public { 40 | _removeDistributor(msg.sender); 41 | } 42 | 43 | // Define an internal function '_addDistributor' to add this role, called by 'addDistributor' 44 | function _addDistributor(address account) internal { 45 | distributors.add(account); 46 | emit DistributorAdded(account); 47 | } 48 | 49 | // Define an internal function '_removeDistributor' to remove this role, called by 'removeDistributor' 50 | function _removeDistributor(address account) internal { 51 | distributors.remove(account); 52 | emit DistributorRemoved(account);(account); 53 | } 54 | } -------------------------------------------------------------------------------- /contracts/pharmaceuticalaccesscontrol/ManufacturerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // Import the library 'Roles' 4 | import "./Roles.sol"; 5 | 6 | // Define a contract 'ManufacturerRole' to manage this role - add, remove, check 7 | contract ManufacturerRole { 8 | using Roles for Roles.Role; 9 | 10 | // Define 2 events, one for Adding, and other for Removing 11 | event ManufacturerAdded(address indexed account); 12 | event ManufacturerRemoved(address indexed account); 13 | 14 | // Define a struct 'manufacturers' by inheriting from 'Roles' library, struct Role 15 | Roles.Role private manufacturers; 16 | 17 | // In the constructor make the address that deploys this contract the 1st manufacturer 18 | constructor() public { 19 | _addManufacturer(msg.sender); 20 | } 21 | 22 | // Define a modifier that checks to see if msg.sender has the appropriate role 23 | modifier onlyManufacturer() { 24 | require(isManufacturer(msg.sender), "sender is not a manufacturer"); 25 | _; 26 | } 27 | 28 | // Define a function 'isManufacturer' to check this role 29 | function isManufacturer(address account) public view returns (bool) { 30 | return manufacturers.has(account); 31 | } 32 | 33 | // Define a function 'addManufacturer' that adds this role 34 | function addManufacturer(address account) public onlyManufacturer { 35 | _addManufacturer(account); 36 | } 37 | 38 | // Define a function 'renounceManufacturer' to renounce this role 39 | function renounceManufacturer() public { 40 | _removeManufacturer(msg.sender); 41 | } 42 | 43 | // Define an internal function '_addManufacturer' to add this role, called by 'addManufacturer' 44 | function _addManufacturer(address account) internal { 45 | manufacturers.add(account); 46 | emit ManufacturerAdded(account); 47 | } 48 | 49 | // Define an internal function '_removeManufacturer' to remove this role, called by 'removeManufacturer' 50 | function _removeManufacturer(address account) internal { 51 | manufacturers.remove(account); 52 | emit ManufacturerRemoved(account); 53 | } 54 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # **Ethereum Pharmaceutical Supply Chain** 2 | 3 | The goals of this project are as follows: 4 | 5 | - Plan the project with write-ups. 6 | - Write smart contracts. 7 | - Test smart contract code coverage 8 | - Deploy smart contract on public test network. 9 | - Create web client to interact with smart contract 10 | 11 | ------ 12 | 13 | 14 | 15 | ## Overview 16 | 17 | This project is a Distributed Application (DApp) which includes an [Ethereum](https://www.ethereum.org/) smart contract and a web front-end. The smart contract represents a pharmaceutical supply chain implemented with [Solidity](https://github.com/ethereum/solidity) . 18 | 19 | 20 | 21 | ------ 22 | 23 | ### Supply Chain Smart Contract 24 | 25 | The Supply Chain smart contract ([SupplyChain.sol](contracts/pharmaceuticalbase/SupplyChain.sol)) is implemented using Solidity. The contract inherits from a contract that provide ownership ([Ownable.sol](contracts/pharmaceuticalcore/Ownable.sol)) functions. And contracts based on the Role contract ([Roles.sol](contracts/pharmaceuticalaccesscontrol/Roles.sol)) that implement role-specific functions ([ManufacturerRole.sol](contracts/pharmaceuticalaccesscontrol/ManufacturerRole.sol), [DistributorRole.sol](contracts/pharmaceuticalaccesscontrol/DistributorRole.sol), [RetailerRole.sol](contracts/pharmaceuticalaccesscontrol/RetailerRole.sol), and [ConsumerRole.sol](contracts/pharmaceuticalaccesscontrol/ConsumerRole.sol)). 26 | 27 | ##### UML Diagrams 28 | 29 | UML diagrams describing the contracts and their interactions are listed below. 30 | 31 | - [State Diagram](docs/State-Diagram.pdf) 32 | - [Activity Diagram](docs/Activity-Diagram.pdf) 33 | - [Sequence Diagram](docs/Sequence-Diagram.pdf) 34 | - [Class Diagram](docs/Class-Diagram.pdf) 35 | 36 | ##### Item Struct 37 | 38 | The Supply Chain contract includes a `struct` named `Item` ([SupplyChain.sol](contracts/pharmaceuticalbase/SupplyChain.sol) lines -38-54) which holds item information including the current owner, manufacturer, distributor, retailer, and consumer information.) 39 | 40 | ------ 41 | 42 | ### Smart Contract Unit Tests 43 | 44 | Unit tests are located within [TestSupplychain.js](test/TestSupplychain.js). They are implemented in JavaScript using the [Truffle](https://truffleframework.com/) framework. The tests can be run by starting [Ganache](https://truffleframework.com/ganache) and executing the following commands (from a Windows Command prompt) from the main project folder. 45 | 46 | ```bash 47 | C:\truffle.cmd develop 48 | 49 | truffle(develop)>test 50 | ``` 51 | 52 | Truffle will compile the solidity contracts located under `contracts` , then deploy the contracts to Ganache and execute the tests located under `test`. An image of the truffle test execution is below. 53 | 54 | ![Truffle tests](images/truffle-test.png) 55 | 56 | 57 | 58 | ------ 59 | 60 | ### Deploying to the Ethereum RINKEBY Test Network 61 | 62 | Truffle is used to deploy the smart contracts to a target network. The truffle configuration file ([truffle-config.js](truffle-config.js)) controls where Truffle deploys the project's contracts. A screenshot of the [truffle-config.js](truffle-config.js) file is shown below. The config file contains deployment parameters for two networks: *development* and *rinkeby*. 63 | 64 | ```javascript 65 | require('dotenv').config(); 66 | var HDWalletProvider = require('truffle-hdwallet-provider'); 67 | var infuraUrl = 'https://rinkeby.infura.io/v3/' + process.env.INFURA_API_KEY; 68 | 69 | module.exports = { 70 | networks: { 71 | development: { 72 | host: '127.0.0.1', 73 | port: 7545, 74 | network_id: "*" 75 | }, 76 | rinkeby: { 77 | provider: function() { 78 | return new HDWalletProvider(process.env.MNEMONIC, infuraUrl) 79 | }, 80 | network_id: 4, 81 | gas: 4500000, 82 | gasPrice: 10000000000, 83 | } 84 | } 85 | }; 86 | ``` 87 | 88 | The *rinkeby* network configuration tells Truffle to deploy the smart contracts to the RINKEBY test network through the [Infura](https://infura.io/) blockchain infrastructure. The wallet mnemonic and RINKEBY API key values are retrieved from the .env file using the [dotenv](https://www.npmjs.com/package/dotenv) node.js module. 89 | 90 | Running the following commands will initiate the deployment to the RINKEBY test network. 91 | 92 | ```bash 93 | cd contracts 94 | truffle migrate --network rinkeby 95 | ``` 96 | 97 | ![truffle migrate](images/deploy-rinkeby.png) 98 | 99 | 100 | 101 | ------ 102 | 103 | ### Web Front-End 104 | 105 | The project includes an HTML test client ([index.html)](src/index.html) that interacts with the contract on the Ethereum RINKEBY test network. The front-end uses the [Web3 Ethereum JavaScript API](https://web3js.readthedocs.io/en/1.0/) to interact with the supply chain contract on the RINKEBY test network through the [MetaMask](https://metamask.io/) browser plug-in. 106 | 107 | Here is a screen shot of the web client. 108 | 109 | ![Supply Chain Client](images/web-client.png) 110 | 111 | The interface interacts with the smart contract to move a drug through the supply chain process; from manufacture to purchase by the end consumer. 112 | 113 | ------ 114 | 115 | 116 | 117 | ## Configuring the Project 118 | 119 | #### Require Software 120 | 121 | The following software must be installed on the host machine. 122 | 123 | - Install [Ganache](https://truffleframework.com/ganache) 124 | 125 | Download and install the version of Ganache for your operating system. 126 | 127 | - Install [Truffle](https://truffleframework.com/truffle) 128 | 129 | Run the following command 130 | 131 | ```bash 132 | npm install truffle -g 133 | ``` 134 | 135 | - Install [MetaMask](https://metamask.io/) 136 | 137 | Install the MetaMask [Chrome extension](https://metamask.io/). 138 | 139 | - Install Node.js dependencies 140 | 141 | The following node installation command should be executed from the project root directory. 142 | 143 | ```bash 144 | npm install 145 | ``` 146 | 147 | #### Infura Account 148 | 149 | The project requires an Infura account. You can create one [here](https://infura.io/). 150 | 151 | 152 | 153 | #### Create an .env file 154 | 155 | The truffle test and migration process requires a file named *.env* to be located in the [root](./) folder. This file must contain your wallet mnemonic and [Infura](https://infura.io/) API URL. 156 | 157 | ```bash 158 | INFURA_API_KEY= 159 | MNEMONIC=" 2 | 3 | 4 | 5 | 6 | 7 | Pharmaceutical Supply Chain 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Pharmaceutical Supply Chain

15 |
16 |

Prove the authenticity of medicine using the Ethereum blockchain.

17 |
18 | 19 |
20 |

Product Overview

21 |
22 | SKU 23 |
24 |
25 | UPC 26 |
27 |
28 | Current Owner ID 29 |
30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 |

Manufacturer Details

38 |
39 | Manufacturer ID 40 |
41 | 43 | 44 |
45 | 46 | Manufacturer Name 47 |
48 |
49 | Manufacturer Information 50 |
51 |
52 | Manufacturer Latitude 53 |
54 |
55 | Manufacturer Longitude 56 |
57 |
58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 |
66 | 67 |
71 | 72 | 73 | 74 |
75 |
76 |

Pharmaceutical Details

77 |
78 | Pharmaceutical Notes 79 |
80 |
81 | Pharmaceutical Price 82 |
83 | ETH
84 | Distributor ID 85 |
86 | 88 | 89 |
90 | Retailer ID 91 |
92 | 94 | 95 |
96 | Consumer ID 97 |
98 | 100 | 101 |
102 |
103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 |
127 |
128 |
129 |
130 | 131 |

Transaction History

132 |
133 |
    134 | 135 |
136 |
137 |
138 |
139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /contracts/pharmaceuticalbase/SupplyChain.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | import "../pharmaceuticalcore/Ownable.sol"; 3 | import "../pharmaceuticalaccesscontrol/ConsumerRole.sol"; 4 | import "../pharmaceuticalaccesscontrol/DistributorRole.sol"; 5 | import "../pharmaceuticalaccesscontrol/ManufacturerRole.sol"; 6 | import "../pharmaceuticalaccesscontrol/RetailerRole.sol"; 7 | 8 | // Define a contract 'Supplychain' 9 | contract SupplyChain is Ownable, ConsumerRole, ManufacturerRole, RetailerRole, DistributorRole { 10 | 11 | // Define a variable called 'upc' for Universal Product Code (UPC) 12 | uint upc; 13 | 14 | // Define a variable called 'sku' for Stock Keeping Unit (SKU) 15 | uint sku; 16 | 17 | // Define a public mapping 'items' that maps the UPC to an Item. 18 | mapping (uint => Item) items; 19 | 20 | // Define a public mapping 'itemsHistory' that maps the UPC to an array of TxHash, 21 | // that track its journey through the supply chain -- to be sent from DApp. 22 | mapping (uint => string[]) itemsHistory; 23 | 24 | // Define enum 'State' with the following values: 25 | enum State { 26 | Manufactured, // 0 27 | Packaged, // 1 28 | ForSale, // 2 29 | Sold, // 3 30 | Shipped, // 4 31 | Received, // 5 32 | Purchased // 6 33 | } 34 | 35 | State constant defaultState = State.Manufactured; 36 | 37 | // Define a struct 'Item' with the following fields: 38 | struct Item { 39 | uint sku; // Stock Keeping Unit (SKU) 40 | uint upc; // Universal Product Code (UPC), generated by the Manufacturer, goes on the package, can be verified by the Consumer 41 | address ownerID; // Metamask-Ethereum address of the current owner as the product moves through 8 stages 42 | address originManufacturerID; // Metamask-Ethereum address of the Manufacturer 43 | string originManufacturerName; // Manufacturer Name 44 | string originManufacturerInformation; // Manufacturer Information 45 | string originManufacturerLatitude; //Manufacturer Latitude 46 | string originManufacturerLongitude; // Manufacturer Longitude 47 | uint productID; // Product ID potentially a combination of upc + sku 48 | string productNotes; // Product Notes 49 | uint productPrice; // Product Price 50 | State itemState; // Product State as represented in the enum above 51 | address distributorID; // Metamask-Ethereum address of the Distributor 52 | address retailerID; // Metamask-Ethereum address of the Retailer 53 | address consumerID; // Metamask-Ethereum address of the Consumer 54 | } 55 | 56 | // Define 8 events with the same 8 state values and accept 'upc' as input argument 57 | event Manufactured(uint upc); 58 | event Packaged(uint upc); 59 | event ForSale(uint upc); 60 | event Sold(uint upc); 61 | event Shipped(uint upc); 62 | event Received(uint upc); 63 | event Purchased(uint upc); 64 | 65 | // Define a modifer that verifies the Caller 66 | modifier verifyCaller (address _address) { 67 | require(msg.sender == _address, "the sender address does not match"); 68 | _; 69 | } 70 | 71 | // Define a modifier that checks if the paid amount is sufficient to cover the price 72 | modifier paidEnough(uint _price) { 73 | require(msg.value >= _price, "insufficient payment"); 74 | _; 75 | } 76 | 77 | // Define a modifier that checks the price and refunds the remaining balance 78 | modifier checkValue(uint _upc) { 79 | _; 80 | uint _price = items[_upc].productPrice; 81 | uint amountToReturn = msg.value - _price; 82 | items[_upc].consumerID.transfer(amountToReturn); 83 | } 84 | 85 | 86 | // Define a modifier that checks if an item.state of a upc is Manufactured 87 | modifier manufactured(uint _upc) { 88 | require(items[_upc].itemState == State.Manufactured, "item not manufactured"); 89 | _; 90 | } 91 | 92 | // Define a modifier that checks if an item.state of a upc is Packed 93 | modifier packaged(uint _upc) { 94 | require(items[_upc].itemState == State.Packaged, "item not packaged"); 95 | _; 96 | } 97 | 98 | // Define a modifier that checks if an item.state of a upc is ForSale 99 | modifier forSale(uint _upc) { 100 | require(items[_upc].itemState == State.ForSale, "item not for sale"); 101 | _; 102 | } 103 | 104 | // Define a modifier that checks if an item.state of a upc is Sold 105 | modifier sold(uint _upc) { 106 | require(items[_upc].itemState == State.Sold, "item not sold"); 107 | _; 108 | } 109 | 110 | // Define a modifier that checks if an item.state of a upc is Shipped 111 | modifier shipped(uint _upc) { 112 | require(items[_upc].itemState == State.Shipped, "item not shipped"); 113 | _; 114 | } 115 | 116 | // Define a modifier that checks if an item.state of a upc is Received 117 | modifier received(uint _upc) { 118 | require(items[_upc].itemState == State.Received, "item not received"); 119 | _; 120 | } 121 | 122 | // Define a modifier that checks if an item.state of a upc is Purchased 123 | modifier purchased(uint _upc) { 124 | require(items[_upc].itemState == State.Purchased, "item not purchased"); 125 | _; 126 | } 127 | 128 | 129 | // Define a modifier that checks if the msg.sender can sell the item. 130 | modifier canSell(uint _upc) { 131 | require(canAccountSellItem(msg.sender, _upc), "the sender cannot sell the item"); 132 | _; 133 | } 134 | 135 | // Define a modifier that checks if the msg.sender can ship the item 136 | modifier canShip(uint _upc) { 137 | require(canAccountShipItem(msg.sender, _upc), "the sender cannot ship the item"); 138 | _; 139 | } 140 | 141 | // Define a modifier that checks if the msg.sender can buy the item 142 | modifier canBuy(uint _upc) { 143 | require(canAccountBuyItem(msg.sender, _upc), "the sender cannot buy the item"); 144 | _; 145 | } 146 | 147 | // Define a modifier that checks if the msg.sender can package the item 148 | modifier canPackage(uint _upc) { 149 | require(canAccountPackageItem(msg.sender, _upc), "the sender cannot package the item"); 150 | _; 151 | } 152 | 153 | // Define a modifier that checks if the msg.sender can receive the item 154 | modifier canReceive(uint _upc) { 155 | require(canAccountReceiveItem(_upc), "the sender cannot receive the item"); 156 | _; 157 | } 158 | 159 | // In the constructor set 'owner' to the address that instantiated the contract 160 | // and set 'sku' to 1 161 | // and set 'upc' to 1 162 | constructor() public payable { 163 | transferOwnership(msg.sender); 164 | upc = 1; 165 | } 166 | 167 | // Define a function 'kill' if required 168 | function kill() public { 169 | if (msg.sender == owner()) { 170 | selfdestruct(owner()); 171 | } 172 | } 173 | 174 | // Define a function 'manufactureItem' that allows a manufacturer to mark an item 'Manufactured' 175 | function manufactureItem(uint _upc, string _originManufacturerName, string _originManufacturerInformation, 176 | string _originManufacturerLatitude, string _originManufacturerLongitude, string _productNotes) public 177 | onlyManufacturer 178 | { 179 | // Add the new item as part of the Manufacturing process 180 | // Increment sku 181 | sku = sku + 1; 182 | // Emit the appropriate event 183 | emit Manufactured(_upc); 184 | 185 | uint productId = sku + _upc; 186 | 187 | // Add the new item to the inventory and mark it as manufactured. 188 | items[_upc] = Item( 189 | {sku: sku, 190 | upc: _upc, 191 | ownerID: msg.sender, 192 | originManufacturerID: msg.sender, 193 | originManufacturerName: _originManufacturerName, 194 | originManufacturerInformation: _originManufacturerInformation, 195 | originManufacturerLatitude: _originManufacturerLatitude, 196 | originManufacturerLongitude: _originManufacturerLongitude, 197 | productID: productId, 198 | productNotes: _productNotes, 199 | productPrice: 0, 200 | itemState: State.Manufactured, 201 | distributorID: 0, 202 | retailerID: 0, 203 | consumerID: 0}); 204 | } 205 | 206 | // Define a function 'packItem' that allows a manufacturer or distributor to mark an item 'Packed' 207 | function packageItem(uint _upc) public 208 | // Call modifier to check if upc has passed previous supply chain stage 209 | canPackage(_upc) 210 | { 211 | // Update the appropriate fields 212 | items[_upc].itemState = State.Packaged; 213 | // Emit the appropriate event 214 | emit Packaged(_upc); 215 | } 216 | 217 | // Define a function 'sellItem' that allows a manufacturer to mark an item 'ForSale' 218 | function sellItem(uint _upc, uint _price) public 219 | // Call modifier to check if upc has passed previous supply chain stage and verify caller of this function 220 | canSell(_upc) 221 | { 222 | // Update the appropriate fields 223 | items[_upc].itemState = State.ForSale; 224 | items[_upc].productPrice = _price; 225 | // Emit the appropriate event 226 | emit ForSale(upc); 227 | } 228 | 229 | // Define a function 'buyItem' that allows the disributor to mark an item 'Sold' 230 | // Use the above defined modifiers to check if the item is available for sale, if the buyer has paid enough, 231 | // and any excess ether sent is refunded back to the buyer 232 | function buyItem(uint _upc) public payable 233 | // Call modifier to check if upc has passed previous supply chain stage 234 | canBuy(_upc) 235 | // Call modifer to check if buyer has paid enough 236 | paidEnough(items[_upc].productPrice) 237 | // Call modifer to send any excess ether back to buyer 238 | checkValue(_upc) 239 | { 240 | address buyer = msg.sender; 241 | uint price = items[sku].productPrice; 242 | 243 | // Update the appropriate fields - ownerID, distributorID, itemState 244 | items[_upc].ownerID = buyer; 245 | 246 | if(items[_upc].distributorID == address(0)) 247 | items[_upc].distributorID = buyer; 248 | else if(items[_upc].retailerID == address(0)) 249 | items[_upc].retailerID = buyer; 250 | else if(items[_upc].consumerID == address(0)) 251 | items[_upc].consumerID = buyer; 252 | 253 | items[_upc].itemState = State.Sold; 254 | 255 | // Transfer money to manufacturer 256 | items[_upc].ownerID.transfer(price); 257 | 258 | // emit the appropriate event 259 | emit Sold(_upc); 260 | } 261 | 262 | // Define a function 'shipItem' that allows the distributor to mark an item 'Shipped' 263 | // Use the above modifers to check if the item is sold 264 | function shipItem(uint _upc) public 265 | // Call modifier to check if upc has passed previous supply chain stage 266 | canShip(_upc) 267 | { 268 | // Update the appropriate fields 269 | items[_upc].itemState = State.Shipped; 270 | // Emit the appropriate event 271 | emit Shipped(_upc); 272 | } 273 | 274 | // Define a function 'receiveItem' that allows the retailer to mark an item 'Received' 275 | // Use the above modifiers to check if the item is shipped 276 | function receiveItem(uint _upc) public 277 | canReceive(_upc) 278 | { 279 | // Update the appropriate fields - ownerID, retailerID, itemState 280 | items[_upc].itemState = State.Received; 281 | 282 | // Emit the appropriate event 283 | emit Received(upc); 284 | } 285 | 286 | // Define a function that determines whether an account can ship the given item. 287 | function canAccountShipItem(address _account, uint _upc) public view returns (bool) { 288 | return (isSold(_upc) && 289 | ((isManufacturer(_account, _upc) && items[_upc].ownerID == items[_upc].distributorID 290 | || isDistributor(_account, _upc) && items[_upc].ownerID == items[_upc].retailerID))); 291 | } 292 | 293 | // Define a function that determines whether an account can package the given item. 294 | function canAccountPackageItem(address _account, uint _upc) public view returns (bool) { 295 | return (isManufactured(_upc) && isManufacturer(_account, _upc)); 296 | } 297 | 298 | // Define a function that determines whether an account can receive the given item. 299 | function canAccountReceiveItem(uint _upc) public view returns (bool) { 300 | return (isItemOwner(msg.sender, _upc) && isShipped(_upc)); 301 | } 302 | 303 | // Define a function that determines whether an account can sell the given item. 304 | function canAccountSellItem(address _account, uint _upc) public view returns (bool) { 305 | return (isItemOwner(_account, _upc) && 306 | ((isManufacturer(_account, _upc) && isPackaged(_upc)) || 307 | (isDistributor(_account, _upc) && isReceived(_upc)) || 308 | (isRetailer(_account, _upc) && isReceived(_upc)))); 309 | } 310 | 311 | // Define a function that determines whether an account can sell the given item. 312 | function canAccountBuyItem(address _account, uint _upc) public view returns (bool) { 313 | return (isForSale(_upc) && 314 | ((isDistributor(_account) && 315 | items[_upc].distributorID == address(0)) || 316 | (isRetailer(_account) && 317 | items[_upc].distributorID != address(0) && 318 | items[_upc].retailerID == address(0)) || 319 | (isConsumer(_account) && 320 | items[_upc].distributorID != address(0) && 321 | items[_upc].retailerID != address(0) && 322 | items[_upc].consumerID == address(0)))); 323 | } 324 | 325 | // Define a function that determines whether the item is packaged. 326 | function isItemOwner(address _account, uint _upc) public view returns (bool) { 327 | return (items[_upc].ownerID == _account); 328 | } 329 | 330 | // Define a function that determines whether the item is packaged. 331 | function isPackaged(uint _upc) public view returns (bool) { 332 | return (items[_upc].itemState == State.Packaged); 333 | } 334 | 335 | // Define a function that determines whether the item is sold. 336 | function isSold(uint _upc) public view returns (bool) { 337 | return (items[_upc].itemState == State.Sold); 338 | } 339 | 340 | // Define a function that determines whether the item is shipped. 341 | function isShipped(uint _upc) public view returns (bool) { 342 | return (items[_upc].itemState == State.Shipped); 343 | } 344 | 345 | // Define a function that determines whether the item is for sale. 346 | function isForSale(uint _upc) public view returns (bool) { 347 | return (items[_upc].itemState == State.ForSale); 348 | } 349 | 350 | // Define a function that determines whether the item was received. 351 | function isReceived(uint _upc) public view returns (bool) { 352 | return (items[_upc].itemState == State.Received); 353 | } 354 | 355 | // Define a function that determines whether the item was manufactured. 356 | function isManufactured(uint _upc) public view returns (bool) { 357 | return (items[_upc].itemState == State.Manufactured); 358 | } 359 | 360 | // Define a function that determines whether the given account manufactured the 361 | // given item. 362 | function isManufacturer(address _account, uint _upc) internal view returns (bool) { 363 | return (isManufacturer(_account) && _account == items[_upc].originManufacturerID); 364 | } 365 | 366 | // Define a function that determines whether the given account is the distributor 367 | // of the given item. 368 | function isDistributor(address _account, uint _upc) internal view returns (bool) { 369 | return (isDistributor(_account) && _account == items[_upc].distributorID); 370 | } 371 | 372 | // Define a function that determines whether the given account is the retailer 373 | // of the given item. 374 | function isRetailer(address _account, uint _upc) internal view returns (bool) { 375 | return (isRetailer(_account) && _account == items[_upc].retailerID); 376 | } 377 | 378 | 379 | // Define a function 'fetchItemBufferOne' that fetches the data 380 | function fetchItemBufferOne(uint _upc) public view returns 381 | ( 382 | uint itemSKU, 383 | uint itemUPC, 384 | address ownerID, 385 | address originManufacturerID, 386 | string originManufacturerName, 387 | string originManufacturerInformation, 388 | string originManufacturerLatitude, 389 | string originManufacturerLongitude 390 | ) 391 | { 392 | // Assign values to the 8 parameters 393 | itemSKU = items[_upc].sku; 394 | itemUPC = items[_upc].upc; 395 | ownerID = items[_upc].ownerID; 396 | originManufacturerID = items[_upc].originManufacturerID; 397 | originManufacturerName = items[_upc].originManufacturerName; 398 | originManufacturerInformation = items[_upc].originManufacturerInformation; 399 | originManufacturerLatitude = items[_upc].originManufacturerLatitude; 400 | originManufacturerLongitude = items[_upc].originManufacturerLongitude; 401 | 402 | return (itemSKU, itemUPC, ownerID, originManufacturerID, originManufacturerName, originManufacturerInformation, 403 | originManufacturerLatitude, originManufacturerLongitude); 404 | } 405 | 406 | // Define a function 'fetchItemBufferTwo' that fetches the data 407 | function fetchItemBufferTwo(uint _upc) public view returns 408 | ( 409 | uint itemSKU, 410 | uint itemUPC, 411 | uint productID, 412 | string productNotes, 413 | uint productPrice, 414 | uint itemState, 415 | address distributorID, 416 | address retailerID, 417 | address consumerID 418 | ) 419 | { 420 | // Assign values to the 9 parameters 421 | itemSKU = items[_upc].sku; 422 | itemUPC = items[_upc].upc; 423 | productID = items[_upc].productID; 424 | productNotes = items[_upc].productNotes; 425 | productPrice = items[_upc].productPrice; 426 | itemState = uint(items[_upc].itemState); 427 | distributorID = items[_upc].distributorID; 428 | retailerID = items[_upc].retailerID; 429 | consumerID = items[_upc].consumerID; 430 | 431 | return (itemSKU, itemUPC, productID, productNotes, productPrice, itemState, distributorID, retailerID, consumerID); 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | App = { 2 | web3Provider: null, 3 | contracts: {}, 4 | emptyAddress: "0x0000000000000000000000000000000000000000", 5 | sku: 0, 6 | upc: 0, 7 | metamaskAccountID: "0x0000000000000000000000000000000000000000", 8 | ownerID: "0x0000000000000000000000000000000000000000", 9 | originManufacturerID: "0x0000000000000000000000000000000000000000", 10 | originManufacturerName: null, 11 | originManufacturerInformation: null, 12 | originManufacturerLatitude: null, 13 | originManufacturerLongitude: null, 14 | productNotes: null, 15 | productPrice: 0, 16 | distributorID: "0x0000000000000000000000000000000000000000", 17 | retailerID: "0x0000000000000000000000000000000000000000", 18 | consumerID: "0x0000000000000000000000000000000000000000", 19 | 20 | init: async function () { 21 | App.readForm(); 22 | /// Setup access to blockchain 23 | return await App.initWeb3(); 24 | }, 25 | 26 | readForm: function () { 27 | App.sku = $("#sku").val(); 28 | App.upc = $("#upc").val(); 29 | App.ownerID = $("#ownerID").val(); 30 | App.originManufacturerID = $("#originManufacturerID").val(); 31 | App.originManufacturerName = $("#originManufacturerName").val(); 32 | App.originManufacturerInformation = $("#originManufacturerInformation").val(); 33 | App.originManufacturerLatitude = $("#originManufacturerLatitude").val(); 34 | App.originManufacturerLongitude = $("#originManufacturerLongitude").val(); 35 | App.productNotes = $("#productNotes").val(); 36 | App.productPrice = $("#productPrice").val(); 37 | App.distributorID = $("#distributorID").val(); 38 | App.retailerID = $("#retailerID").val(); 39 | App.consumerID = $("#consumerID").val(); 40 | 41 | console.log( 42 | App.sku, 43 | App.upc, 44 | App.ownerID, 45 | App.originManufacturerID, 46 | App.originManufacturerName, 47 | App.originManufacturerInformation, 48 | App.originManufacturerLatitude, 49 | App.originManufacturerLongitude, 50 | App.productNotes, 51 | App.productPrice, 52 | App.distributorID, 53 | App.retailerID, 54 | App.consumerID 55 | ); 56 | }, 57 | 58 | initWeb3: async function () { 59 | /// Find or Inject Web3 Provider 60 | /// Modern dapp browsers... 61 | if (window.ethereum) { 62 | App.web3Provider = window.ethereum; 63 | try { 64 | // Request account access 65 | await window.ethereum.enable(); 66 | } catch (error) { 67 | // User denied account access... 68 | console.error("User denied account access") 69 | } 70 | } 71 | // Legacy dapp browsers... 72 | else if (window.web3) { 73 | App.web3Provider = window.web3.currentProvider; 74 | } 75 | // If no injected web3 instance is detected, fall back to Ganache 76 | else { 77 | App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); 78 | } 79 | 80 | App.getMetaskAccountID(); 81 | 82 | return App.initSupplyChain(); 83 | }, 84 | 85 | getMetaskAccountID: function () { 86 | web3 = new Web3(App.web3Provider); 87 | 88 | // Retrieving accounts 89 | web3.eth.getAccounts(function(err, res) { 90 | if (err) { 91 | console.log('Error:',err); 92 | return; 93 | } 94 | console.log('getMetaskID:',res); 95 | App.metamaskAccountID = res[0]; 96 | }) 97 | }, 98 | 99 | initSupplyChain: function () { 100 | /// Source the truffle compiled smart contracts 101 | var jsonSupplyChain='../../build/contracts/SupplyChain.json'; 102 | 103 | /// JSONfy the smart contracts 104 | $.getJSON(jsonSupplyChain, function(data) { 105 | console.log('data',data); 106 | var SupplyChainArtifact = data; 107 | App.contracts.SupplyChain = TruffleContract(SupplyChainArtifact); 108 | App.contracts.SupplyChain.setProvider(App.web3Provider); 109 | 110 | App.fetchItemBufferOne(); 111 | App.fetchItemBufferTwo(); 112 | App.fetchEvents(); 113 | 114 | }); 115 | 116 | return App.bindEvents(); 117 | }, 118 | 119 | bindEvents: function() { 120 | $(document).on('click', App.handleButtonClick); 121 | }, 122 | 123 | handleButtonClick: async function(event) { 124 | event.preventDefault(); 125 | 126 | App.getMetaskAccountID(); 127 | 128 | var processId = parseInt($(event.target).data('id')); 129 | console.log('processId',processId); 130 | 131 | switch(processId) { 132 | case 1: 133 | return await App.manufactureItem(event); 134 | break; 135 | case 2: 136 | return await App.manufacturerPackageItem(event); 137 | break; 138 | case 3: 139 | return await App.manufacturerSellItem(event); 140 | break; 141 | case 4: 142 | return await App.distributorBuyItem(event); 143 | break; 144 | case 5: 145 | return await App.manufacturerShipItem(event); 146 | break; 147 | case 6: 148 | return await App.distributorReceiveItem(event); 149 | break; 150 | case 7: 151 | return await App.distributorSellItem(event); 152 | break; 153 | case 8: 154 | return await App.retailerBuyItem(event); 155 | break; 156 | case 9: 157 | return await App.distributorShipItem(event); 158 | break; 159 | case 10: 160 | return await App.retailerReceiveItem(event); 161 | break; 162 | case 11: 163 | return await App.retailerSellItem(event); 164 | break; 165 | case 12: 166 | return await App.consumerBuyItem(event); 167 | break; 168 | case 13: 169 | return await App.fetchItemBufferOne(event); 170 | break; 171 | case 14: 172 | return await App.fetchItemBufferTwo(event); 173 | break; 174 | case 15: 175 | return await App.addManufacturer(event); 176 | break; 177 | case 16: 178 | return await App.addDistributor(event); 179 | break; 180 | case 17: 181 | return await App.addRetailer(event); 182 | break; 183 | case 18: 184 | return await App.addConsumer(event); 185 | break; 186 | } 187 | 188 | }, 189 | 190 | addConsumer: function(event) { 191 | event.preventDefault(); 192 | var processId = parseInt($(event.target).data('id')); 193 | 194 | App.contracts.SupplyChain.deployed().then(function(instance) { 195 | return instance.addConsumer(App.consumerID, {from: App.ownerID}); 196 | }).then(function(result) { 197 | $("#ftc-item").text(result); 198 | console.log('addConsumer',result); 199 | }).catch(function(err) { 200 | console.log(err.message); 201 | }); 202 | }, 203 | 204 | addRetailer: function(event) { 205 | event.preventDefault(); 206 | var processId = parseInt($(event.target).data('id')); 207 | 208 | App.contracts.SupplyChain.deployed().then(function(instance) { 209 | return instance.addRetailer(App.retailerID, {from: App.ownerID}); 210 | }).then(function(result) { 211 | $("#ftc-item").text(result); 212 | console.log('addRetailer',result); 213 | }).catch(function(err) { 214 | console.log(err.message); 215 | }); 216 | }, 217 | 218 | addDistributor: function(event) { 219 | event.preventDefault(); 220 | var processId = parseInt($(event.target).data('id')); 221 | 222 | App.contracts.SupplyChain.deployed().then(function(instance) { 223 | return instance.addDistributor(App.distributorID, {from: App.ownerID}); 224 | }).then(function(result) { 225 | $("#ftc-item").text(result); 226 | console.log('addDistributor',result); 227 | }).catch(function(err) { 228 | console.log(err.message); 229 | }); 230 | }, 231 | 232 | addManufacturer: function(event) { 233 | event.preventDefault(); 234 | var processId = parseInt($(event.target).data('id')); 235 | 236 | App.contracts.SupplyChain.deployed().then(function(instance) { 237 | return instance.addManufacturer(App.originManufacturerID, {from: App.ownerID}); 238 | }).then(function(result) { 239 | $("#ftc-item").text(result); 240 | console.log('addManufacturer',result); 241 | }).catch(function(err) { 242 | console.log(err.message); 243 | }); 244 | }, 245 | 246 | manufactureItem: function(event) { 247 | event.preventDefault(); 248 | var processId = parseInt($(event.target).data('id')); 249 | App.contracts.SupplyChain.deployed().then(function(instance) { 250 | return instance.manufactureItem( 251 | App.upc, 252 | App.originManufacturerID, 253 | App.originManufacturerName, 254 | App.originManufacturerInformation, 255 | App.originManufacturerLatitude, 256 | App.originManufacturerLongitude, 257 | App.productNotes, 258 | {from: App.originManufacturerID} 259 | ); 260 | }).then(function(result) { 261 | $("#ftc-item").text(result); 262 | console.log('manufactureItem',result); 263 | }).catch(function(err) { 264 | console.log(err.message); 265 | }); 266 | }, 267 | 268 | manufacturerPackageItem: function (event) { 269 | event.preventDefault(); 270 | var processId = parseInt($(event.target).data('id')); 271 | 272 | App.contracts.SupplyChain.deployed().then(function(instance) { 273 | return instance.packageItem(App.upc, {from: App.originManufacturerID}); 274 | }).then(function(result) { 275 | $("#ftc-item").text(result); 276 | console.log('manufacturer - packageItem',result); 277 | }).catch(function(err) { 278 | console.log(err.message); 279 | }); 280 | }, 281 | 282 | manufacturerSellItem: function (event) { 283 | event.preventDefault(); 284 | var processId = parseInt($(event.target).data('id')); 285 | 286 | App.contracts.SupplyChain.deployed().then(function(instance) { 287 | const productPrice = web3.toWei(".0000001", "ether"); 288 | console.log('productPrice',productPrice); 289 | return instance.sellItem(App.upc, App.productPrice, {from: App.originManufacturerID}); 290 | }).then(function(result) { 291 | $("#ftc-item").text(result); 292 | console.log('manufacturer - sellItem',result); 293 | }).catch(function(err) { 294 | console.log(err.message); 295 | }); 296 | }, 297 | 298 | distributorBuyItem: function (event) { 299 | event.preventDefault(); 300 | var processId = parseInt($(event.target).data('id')); 301 | 302 | App.contracts.SupplyChain.deployed().then(function(instance) { 303 | const walletValue = web3.toWei(".0000002", "ether"); 304 | return instance.buyItem(App.upc, {from: App.distributorID, value: walletValue}); 305 | }).then(function(result) { 306 | $("#ftc-item").text(result); 307 | console.log('distributor - buyItem',result); 308 | }).catch(function(err) { 309 | console.log(err.message); 310 | }); 311 | }, 312 | 313 | manufacturerShipItem: function (event) { 314 | event.preventDefault(); 315 | var processId = parseInt($(event.target).data('id')); 316 | 317 | App.contracts.SupplyChain.deployed().then(function(instance) { 318 | return instance.shipItem(App.upc, {from: App.originManufacturerID}); 319 | }).then(function(result) { 320 | $("#ftc-item").text(result); 321 | console.log('manufacturer - shipItem',result); 322 | }).catch(function(err) { 323 | console.log(err.message); 324 | }); 325 | }, 326 | 327 | distributorReceiveItem: function (event) { 328 | event.preventDefault(); 329 | var processId = parseInt($(event.target).data('id')); 330 | 331 | App.contracts.SupplyChain.deployed().then(function(instance) { 332 | return instance.receiveItem(App.upc, {from: App.distributorID}); 333 | }).then(function(result) { 334 | $("#ftc-item").text(result); 335 | console.log('distributor - receiveItem',result); 336 | }).catch(function(err) { 337 | console.log(err.message); 338 | }); 339 | }, 340 | 341 | distributorSellItem: function (event) { 342 | event.preventDefault(); 343 | var processId = parseInt($(event.target).data('id')); 344 | 345 | App.contracts.SupplyChain.deployed().then(function(instance) { 346 | const productPrice = web3.toWei(.0000002, "ether"); 347 | console.log('productPrice',productPrice); 348 | return instance.sellItem(App.upc, App.productPrice, {from: App.distributorID}); 349 | }).then(function(result) { 350 | $("#ftc-item").text(result); 351 | console.log('distributor - sellItem',result); 352 | }).catch(function(err) { 353 | console.log(err.message); 354 | }); 355 | }, 356 | 357 | retailerBuyItem: function (event) { 358 | event.preventDefault(); 359 | var processId = parseInt($(event.target).data('id')); 360 | 361 | App.contracts.SupplyChain.deployed().then(function(instance) { 362 | const walletValue = web3.toWei(".0000004", "ether"); 363 | return instance.buyItem(App.upc, {from: App.retailerID, value: walletValue}); 364 | }).then(function(result) { 365 | $("#ftc-item").text(result); 366 | console.log('retailer - buyItem',result); 367 | }).catch(function(err) { 368 | console.log(err.message); 369 | }); 370 | }, 371 | 372 | distributorShipItem: function (event) { 373 | event.preventDefault(); 374 | var processId = parseInt($(event.target).data('id')); 375 | 376 | App.contracts.SupplyChain.deployed().then(function(instance) { 377 | return instance.shipItem(App.upc, {from: App.distributorID}); 378 | }).then(function(result) { 379 | $("#ftc-item").text(result); 380 | console.log('dsitrbutor - shipItem',result); 381 | }).catch(function(err) { 382 | console.log(err.message); 383 | }); 384 | }, 385 | 386 | retailerReceiveItem: function (event) { 387 | event.preventDefault(); 388 | var processId = parseInt($(event.target).data('id')); 389 | 390 | App.contracts.SupplyChain.deployed().then(function(instance) { 391 | return instance.receiveItem(App.upc, {from: App.retailerID}); 392 | }).then(function(result) { 393 | $("#ftc-item").text(result); 394 | console.log('retailer - receiveItem',result); 395 | }).catch(function(err) { 396 | console.log(err.message); 397 | }); 398 | }, 399 | 400 | retailerSellItem: function (event) { 401 | event.preventDefault(); 402 | var processId = parseInt($(event.target).data('id')); 403 | 404 | App.contracts.SupplyChain.deployed().then(function(instance) { 405 | const productPrice = web3.toWei(.0000003, "ether"); 406 | console.log('productPrice',productPrice); 407 | return instance.sellItem(App.upc, App.productPrice, {from: App.retailerID}); 408 | }).then(function(result) { 409 | $("#ftc-item").text(result); 410 | console.log('retailer - sellItem',result); 411 | }).catch(function(err) { 412 | console.log(err.message); 413 | }); 414 | }, 415 | 416 | consumerBuyItem: function (event) { 417 | event.preventDefault(); 418 | var processId = parseInt($(event.target).data('id')); 419 | 420 | App.contracts.SupplyChain.deployed().then(function(instance) { 421 | const walletValue = web3.toWei(".0000004", "ether"); 422 | return instance.buyItem(App.upc, {from: App.consumerID, value: walletValue}); 423 | }).then(function(result) { 424 | $("#ftc-item").text(result); 425 | console.log('consumer - buyItem',result); 426 | }).catch(function(err) { 427 | console.log(err.message); 428 | }); 429 | }, 430 | 431 | fetchItemBufferOne: function () { 432 | /// event.preventDefault(); 433 | /// var processId = parseInt($(event.target).data('id')); 434 | App.upc = $('#upc').val(); 435 | console.log('upc',App.upc); 436 | 437 | App.contracts.SupplyChain.deployed().then(function(instance) { 438 | return instance.fetchItemBufferOne(App.upc); 439 | }).then(function(result) { 440 | $("#ftc-item").text(result); 441 | console.log('fetchItemBufferOne', result); 442 | }).catch(function(err) { 443 | console.log(err.message); 444 | }); 445 | }, 446 | 447 | 448 | fetchItemBufferTwo: function () { 449 | /// event.preventDefault(); 450 | /// var processId = parseInt($(event.target).data('id')); 451 | 452 | App.contracts.SupplyChain.deployed().then(function(instance) { 453 | return instance.fetchItemBufferTwo.call(App.upc); 454 | }).then(function(result) { 455 | $("#ftc-item").text(result); 456 | console.log('fetchItemBufferTwo', result); 457 | }).catch(function(err) { 458 | console.log(err.message); 459 | }); 460 | }, 461 | 462 | fetchEvents: function () { 463 | if (typeof App.contracts.SupplyChain.currentProvider.sendAsync !== "function") { 464 | App.contracts.SupplyChain.currentProvider.sendAsync = function () { 465 | return App.contracts.SupplyChain.currentProvider.send.apply( 466 | App.contracts.SupplyChain.currentProvider, 467 | arguments 468 | ); 469 | }; 470 | } 471 | 472 | App.contracts.SupplyChain.deployed().then(function(instance) { 473 | var events = instance.allEvents(function(err, log){ 474 | if (!err) 475 | $("#ftc-events").append('
  • ' + log.event + ' - ' + log.transactionHash + '
  • '); 476 | }); 477 | }).catch(function(err) { 478 | console.log(err.message); 479 | }); 480 | 481 | } 482 | }; 483 | 484 | $(function () { 485 | $(window).load(function () { 486 | App.init(); 487 | }); 488 | }); 489 | -------------------------------------------------------------------------------- /test/TestSupplychain.js: -------------------------------------------------------------------------------- 1 | // This script is designed to test the solidity smart contract - SuppyChain.sol -- and the various functions within 2 | // Declare a variable and assign the compiled smart contract artifact 3 | var SupplyChain = artifacts.require('SupplyChain') 4 | 5 | contract('SupplyChain', function(accounts) { 6 | // Declare few constants and assign a few sample accounts generated by ganache-cli 7 | var sku = 1 8 | var upc = 1 9 | const ownerID = accounts[0] 10 | const originManufacturerID = accounts[1] 11 | const originManufacturerName = "Pharma Company" 12 | const originManufacturerInformation = "Pharma Company Info" 13 | const originManufacturerLatitude = "-30.3079827" 14 | const originManufacturerLongitude = "-97.893485" 15 | var productID = sku + upc 16 | const productNotes = "Acetylsalicylic acid" 17 | const productPrice = web3.toWei(1, "ether") 18 | var itemState = 0 19 | const distributorID = accounts[2] 20 | const retailerID = accounts[3] 21 | const consumerID = accounts[4] 22 | const emptyAddress = '0x0000000000000000000000000000000000000000' 23 | 24 | const stateManufactured = 0; 25 | const statePackaged = 1; 26 | const stateForSale = 2; 27 | const stateSold = 3; 28 | const stateShipped = 4; 29 | const stateReceived = 5; 30 | const statePurchased = 6; 31 | 32 | 33 | ///Available Accounts 34 | ///================== 35 | ///(0) 0x27d8d15cbc94527cadf5ec14b69519ae23288b95 36 | ///(1) 0x018c2dabef4904ecbd7118350a0c54dbeae3549a 37 | ///(2) 0xce5144391b4ab80668965f2cc4f2cc102380ef0a 38 | ///(3) 0x460c31107dd048e34971e57da2f99f659add4f02 39 | ///(4) 0xd37b7b8c62be2fdde8daa9816483aebdbd356088 40 | ///(5) 0x27f184bdc0e7a931b507ddd689d76dba10514bcb 41 | ///(6) 0xfe0df793060c49edca5ac9c104dd8e3375349978 42 | ///(7) 0xbd58a85c96cc6727859d853086fe8560bc137632 43 | ///(8) 0xe07b5ee5f738b2f87f88b99aac9c64ff1e0c7917 44 | ///(9) 0xbd3ff2e3aded055244d66544c9c059fa0851da44 45 | 46 | console.log("ganache-cli accounts used here...") 47 | console.log("Contract Owner: accounts[0] ", accounts[0]) 48 | console.log("Manufacturer: accounts[1] ", accounts[1]) 49 | console.log("Distributor: accounts[2] ", accounts[2]) 50 | console.log("Retailer: accounts[3] ", accounts[3]) 51 | console.log("Consumer: accounts[4] ", accounts[4]) 52 | 53 | let supplyChainContract = null; 54 | 55 | 56 | 57 | 58 | it("Testing smart contract function manufactureItem() that allows a Manufacturer to manufacture a drug", async() => { 59 | 60 | supplyChainContract = await SupplyChain.deployed() 61 | 62 | // set up account membership 63 | if(!await supplyChainContract.isManufacturer(originManufacturerID)) 64 | await supplyChainContract.addManufacturer(originManufacturerID); 65 | 66 | if(!await supplyChainContract.isDistributor(distributorID)) 67 | await supplyChainContract.addDistributor(distributorID); 68 | 69 | if(!await supplyChainContract.isRetailer(retailerID)) 70 | await supplyChainContract.addRetailer(retailerID); 71 | 72 | if(!await supplyChainContract.isConsumer(consumerID)) 73 | await supplyChainContract.addConsumer(consumerID); 74 | 75 | 76 | if(!await supplyChainContract.isManufacturer(originManufacturerID)) 77 | await supplyChainContract.addManufacturer(originManufacturerID) 78 | 79 | let isManufacturer = await supplyChainContract.isManufacturer(originManufacturerID); 80 | assert.equal(isManufacturer, true); 81 | 82 | let resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 83 | let sku = parseInt(resultBufferOne) + 1 84 | 85 | // Declare and Initialize a variable for event 86 | var eventEmitted = false 87 | 88 | // Watch the emitted event Manufactured() 89 | var event = supplyChainContract.Manufactured(); 90 | await event.watch((err, res) => { 91 | eventEmitted = true 92 | }) 93 | 94 | // Mark an item as Manufactured by calling function manufactureItem() 95 | await supplyChainContract.manufactureItem(upc, originManufacturerID, originManufacturerName, originManufacturerInformation, originManufacturerLatitude, originManufacturerLongitude, productNotes, {from: originManufacturerID}) 96 | 97 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 98 | resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 99 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 100 | 101 | // Verify the result set 102 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 103 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 104 | assert.equal(resultBufferOne[2], originManufacturerID, 'Error: Missing or Invalid ownerID') 105 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 106 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 107 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 108 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 109 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 110 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 111 | assert.equal(resultBufferTwo[5], stateManufactured, 'Error: Invalid item State') 112 | assert.equal(resultBufferTwo[6], emptyAddress, 'Error: Invalid distributorID') 113 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 114 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 115 | assert.equal(eventEmitted, true, 'Invalid event emitted') 116 | }) 117 | 118 | 119 | // 2nd Test 120 | it("Testing smart contract function packageItem() that allows a Manufacturer to package a drug", async() => { 121 | 122 | // Declare and Initialize a variable for event 123 | var eventEmitted = false 124 | 125 | // Watch the emitted event Manufactured() 126 | var event = supplyChainContract.Packaged() 127 | await event.watch((err, res) => { 128 | eventEmitted = true 129 | }) 130 | 131 | // Mark an item as Manufactured by calling function manufactureItem() 132 | await supplyChainContract.packageItem(upc, {from: originManufacturerID}) 133 | 134 | 135 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 136 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 137 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 138 | 139 | // Verify the result set 140 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 141 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 142 | assert.equal(resultBufferOne[2], originManufacturerID, 'Error: Missing or Invalid ownerID') 143 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 144 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 145 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 146 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 147 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 148 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 149 | assert.equal(resultBufferTwo[5], statePackaged, 'Error: Invalid item State') 150 | assert.equal(resultBufferTwo[6], emptyAddress, 'Error: Invalid distributorID') 151 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 152 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 153 | assert.equal(eventEmitted, true, 'Invalid event emitted') 154 | }) 155 | 156 | it("Testing smart contract function sellItem() that allows a manufcturer to sell a drug", async() => { 157 | 158 | // Declare and Initialize a variable for event 159 | var eventEmitted = false 160 | 161 | // Watch the emitted event Manufactured() 162 | var event = supplyChainContract.ForSale() 163 | await event.watch((err, res) => { 164 | eventEmitted = true 165 | }) 166 | 167 | // Mark an item as Manufactured by calling function manufactureItem() 168 | await supplyChainContract.sellItem(upc, productPrice, {from: originManufacturerID}) 169 | 170 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 171 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 172 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 173 | 174 | // Verify the result set 175 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 176 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 177 | assert.equal(resultBufferOne[2], originManufacturerID, 'Error: Missing or Invalid ownerID') 178 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 179 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 180 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 181 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 182 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 183 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 184 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 185 | assert.equal(resultBufferTwo[5], stateForSale, 'Error: Invalid item State') 186 | assert.equal(resultBufferTwo[6], emptyAddress, 'Error: Invalid distributorID') 187 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 188 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 189 | assert.equal(eventEmitted, true, 'Invalid event emitted') 190 | 191 | }); 192 | 193 | 194 | 195 | it("Testing smart contract function buyItem() that allows a distributor to buy a drug", async() => { 196 | 197 | // Declare and Initialize a variable for event 198 | var eventEmitted = false 199 | 200 | // Watch the emitted event Manufactured() 201 | var event = supplyChainContract.Sold() 202 | await event.watch((err, res) => { 203 | eventEmitted = true 204 | }) 205 | 206 | // Mark an item as Manufactured by calling function manufactureItem() 207 | await supplyChainContract.buyItem(upc, {from: distributorID, value:productPrice}) 208 | 209 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 210 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 211 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 212 | 213 | // Verify the result set 214 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 215 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 216 | assert.equal(resultBufferOne[2], distributorID, 'Error: Missing or Invalid ownerID') 217 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 218 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 219 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 220 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 221 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 222 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 223 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 224 | assert.equal(resultBufferTwo[5], stateSold, 'Error: Invalid item State') 225 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 226 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 227 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 228 | assert.equal(eventEmitted, true, 'Invalid event emitted') 229 | 230 | }); 231 | 232 | 233 | it("Testing smart contract function shipItem() that allows a distributor to ship a drug", async() => { 234 | 235 | // Declare and Initialize a variable for event 236 | var eventEmitted = false 237 | 238 | // Watch the emitted event Manufactured() 239 | var event = supplyChainContract.Shipped() 240 | await event.watch((err, res) => { 241 | eventEmitted = true 242 | }) 243 | 244 | // Mark an item as Manufactured by calling function manufactureItem() 245 | await supplyChainContract.shipItem(upc, {from: originManufacturerID}) 246 | 247 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 248 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 249 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 250 | 251 | // Verify the result set 252 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 253 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 254 | assert.equal(resultBufferOne[2], distributorID, 'Error: Missing or Invalid ownerID') 255 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 256 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 257 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 258 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 259 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 260 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 261 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 262 | assert.equal(resultBufferTwo[5], stateShipped, 'Error: Invalid item State') 263 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 264 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 265 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 266 | assert.equal(eventEmitted, true, 'Invalid event emitted') 267 | }); 268 | 269 | 270 | 271 | it("Testing smart contract function buyItem() that allows a retailer to receive a drug", async() => { 272 | 273 | // Declare and Initialize a variable for event 274 | var eventEmitted = false 275 | 276 | // Watch the emitted event Manufactured() 277 | var event = supplyChainContract.Received() 278 | await event.watch((err, res) => { 279 | eventEmitted = true 280 | }) 281 | 282 | // Mark an item as Manufactured by calling function manufactureItem() 283 | await supplyChainContract.receiveItem(upc, {from: distributorID}) 284 | 285 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 286 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 287 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 288 | 289 | // Verify the result set 290 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 291 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 292 | assert.equal(resultBufferOne[2], distributorID, 'Error: Missing or Invalid ownerID') 293 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 294 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 295 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 296 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 297 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 298 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 299 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 300 | assert.equal(resultBufferTwo[5], stateReceived, 'Error: Invalid item State') 301 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 302 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 303 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 304 | assert.equal(eventEmitted, true, 'Invalid event emitted') 305 | }) 306 | 307 | 308 | it("Testing smart contract function sellItem() that allows a ditributor to sell a drug", async() => { 309 | 310 | // Declare and Initialize a variable for event 311 | var eventEmitted = false 312 | 313 | // Watch the emitted event Manufactured() 314 | var event = supplyChainContract.ForSale() 315 | await event.watch((err, res) => { 316 | eventEmitted = true 317 | }) 318 | 319 | // Mark an item as Manufactured by calling function manufactureItem() 320 | await supplyChainContract.sellItem(upc, productPrice, {from: distributorID}) 321 | 322 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 323 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 324 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 325 | 326 | // Verify the result set 327 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 328 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 329 | assert.equal(resultBufferOne[2], distributorID, 'Error: Missing or Invalid ownerID') 330 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 331 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 332 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 333 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 334 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 335 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 336 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 337 | assert.equal(resultBufferTwo[5], stateForSale, 'Error: Invalid item State') 338 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 339 | assert.equal(resultBufferTwo[7], emptyAddress, 'Error: Invalid retailerID') 340 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 341 | assert.equal(eventEmitted, true, 'Invalid event emitted') 342 | }) 343 | 344 | 345 | it("Testing smart contract function buyItem() that allows a retailer to buy a drug", async() => { 346 | 347 | // Declare and Initialize a variable for event 348 | var eventEmitted = false 349 | 350 | // Watch the emitted event Manufactured() 351 | var event = supplyChainContract.Sold() 352 | await event.watch((err, res) => { 353 | eventEmitted = true 354 | }) 355 | 356 | // Mark an item as Manufactured by calling function manufactureItem() 357 | await supplyChainContract.buyItem(upc, {from: retailerID, value: productPrice}) 358 | 359 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 360 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 361 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 362 | 363 | // Verify the result set 364 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 365 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 366 | assert.equal(resultBufferOne[2], retailerID, 'Error: Missing or Invalid ownerID') 367 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 368 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 369 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 370 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 371 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 372 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 373 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 374 | assert.equal(resultBufferTwo[5], stateSold, 'Error: Invalid item State') 375 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 376 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid retailerID') 377 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 378 | assert.equal(eventEmitted, true, 'Invalid event emitted') 379 | }) 380 | 381 | it("Testing smart contract function shipItem() that allows a distibutor to ship a drug", async() => { 382 | 383 | // Declare and Initialize a variable for event 384 | var eventEmitted = false 385 | 386 | // Watch the emitted event Manufactured() 387 | var event = supplyChainContract.Shipped() 388 | await event.watch((err, res) => { 389 | eventEmitted = true 390 | }) 391 | 392 | // Mark an item as Manufactured by calling function manufactureItem() 393 | await supplyChainContract.shipItem(upc, {from: distributorID}) 394 | 395 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 396 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 397 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 398 | 399 | // Verify the result set 400 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 401 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 402 | assert.equal(resultBufferOne[2], retailerID, 'Error: Missing or Invalid ownerID') 403 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 404 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 405 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 406 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 407 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 408 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 409 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 410 | assert.equal(resultBufferTwo[5], stateShipped, 'Error: Invalid item State') 411 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 412 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid retailerID') 413 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 414 | assert.equal(eventEmitted, true, 'Invalid event emitted') 415 | }) 416 | 417 | it("Testing smart contract function receiveItem() that allows a retailer to receive a drug", async() => { 418 | 419 | // Declare and Initialize a variable for event 420 | var eventEmitted = false 421 | 422 | // Watch the emitted event Manufactured() 423 | var event = supplyChainContract.Received() 424 | await event.watch((err, res) => { 425 | eventEmitted = true 426 | }) 427 | 428 | // Mark an item as Manufactured by calling function manufactureItem() 429 | await supplyChainContract.receiveItem(upc, {from: retailerID}) 430 | 431 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 432 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 433 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 434 | 435 | // Verify the result set 436 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 437 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 438 | assert.equal(resultBufferOne[2], retailerID, 'Error: Missing or Invalid ownerID') 439 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 440 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 441 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 442 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 443 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 444 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 445 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 446 | assert.equal(resultBufferTwo[5], stateReceived, 'Error: Invalid item State') 447 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 448 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid retailerID') 449 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 450 | assert.equal(eventEmitted, true, 'Invalid event emitted') 451 | }) 452 | 453 | it("Testing smart contract function sellItem() that allows a retailer to sell a drug", async() => { 454 | 455 | // Declare and Initialize a variable for event 456 | var eventEmitted = false 457 | 458 | // Watch the emitted event Manufactured() 459 | var event = supplyChainContract.ForSale() 460 | await event.watch((err, res) => { 461 | eventEmitted = true 462 | }) 463 | 464 | // Mark an item as Manufactured by calling function manufactureItem() 465 | await supplyChainContract.sellItem(upc, productPrice, {from: retailerID}) 466 | 467 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 468 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 469 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 470 | 471 | // Verify the result set 472 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 473 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 474 | assert.equal(resultBufferOne[2], retailerID, 'Error: Missing or Invalid ownerID') 475 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 476 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 477 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 478 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 479 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 480 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 481 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 482 | assert.equal(resultBufferTwo[5], stateForSale, 'Error: Invalid item State') 483 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 484 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid retailerID') 485 | assert.equal(resultBufferTwo[8], emptyAddress, 'Error: Invalid consumerID') 486 | assert.equal(eventEmitted, true, 'Invalid event emitted') 487 | }) 488 | 489 | it("Testing smart contract function buyItem() that allows a consumer to buy a drug", async() => { 490 | 491 | // Declare and Initialize a variable for event 492 | var eventEmitted = false 493 | 494 | // Watch the emitted event Manufactured() 495 | var event = supplyChainContract.Sold() 496 | await event.watch((err, res) => { 497 | eventEmitted = true 498 | }) 499 | 500 | // Mark an item as Manufactured by calling function manufactureItem() 501 | await supplyChainContract.buyItem(upc, {from: consumerID, value: productPrice}) 502 | 503 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 504 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne.call(upc) 505 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 506 | 507 | // Verify the result set 508 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU') 509 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 510 | assert.equal(resultBufferOne[2], consumerID, 'Error: Missing or Invalid ownerID') 511 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 512 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 513 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 514 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 515 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 516 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 517 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 518 | assert.equal(resultBufferTwo[5], stateSold, 'Error: Invalid item State') 519 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID') 520 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid retailerID') 521 | assert.equal(resultBufferTwo[8], consumerID, 'Error: Invalid consumerID') 522 | assert.equal(eventEmitted, true, 'Invalid event emitted') 523 | }) 524 | 525 | 526 | it("Testing smart contract function fetchItemBufferOne() that allows anyone to fetch item details from blockchain", async() => { 527 | 528 | 529 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 530 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 531 | const resultBufferOne = await supplyChainContract.fetchItemBufferOne(upc) 532 | 533 | // Verify the result set: 534 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC') 535 | assert.equal(resultBufferOne[2], consumerID, 'Error: Missing or Invalid ownerID') 536 | assert.equal(resultBufferOne[3], originManufacturerID, 'Error: Missing or Invalid originManufacturerID') 537 | assert.equal(resultBufferOne[4], originManufacturerName, 'Error: Missing or Invalid originManufacturerName') 538 | assert.equal(resultBufferOne[5], originManufacturerInformation, 'Error: Missing or Invalid originManufacturerInformation') 539 | assert.equal(resultBufferOne[6], originManufacturerLatitude, 'Error: Missing or Invalid originManufacturerLatitude') 540 | assert.equal(resultBufferOne[7], originManufacturerLongitude, 'Error: Missing or Invalid originManufacturerLongitude') 541 | }) 542 | 543 | 544 | it("Testing smart contract function fetchItemBufferTwo() that allows anyone to fetch item details from blockchain", async() => { 545 | 546 | // Retrieve the just now saved item from blockchain by calling function fetchItem() 547 | const resultBufferTwo = await supplyChainContract.fetchItemBufferTwo.call(upc) 548 | 549 | // Verify the result set: 550 | assert.equal(resultBufferTwo[0], sku, 'Error: Invalid item SKU') 551 | assert.equal(resultBufferTwo[1], upc, 'Error: Invalid item UPC') 552 | assert.equal(resultBufferTwo[2], productID, 'Error: Missing or Invalid productID') 553 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Missing or Invalid productNotes') 554 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Missing or Invalid productPrice') 555 | assert.equal(resultBufferTwo[5], stateSold, 'Error: Invalid item state') 556 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Missing or Invalid distributorID') 557 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Missing or Invalid retailerID') 558 | assert.equal(resultBufferTwo[8], consumerID, 'Error: Missing or Invalid consumerID') 559 | }) 560 | 561 | 562 | }); 563 | --------------------------------------------------------------------------------