From 4d85f9b35470c504c7b7fcced4e59f8902b6d1b6 Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Thu, 23 Sep 2021 17:31:48 +0000
Subject: [PATCH] Initial commit
---
.github/workflows/node.js.yml | 47 +++++
.gitignore | 2 +
README.md | 121 +++++++++++
contracts/Migrations.sol | 19 ++
contracts/SupplyChain.sol | 133 ++++++++++++
migrations/1_initial_migration.js | 5 +
migrations/2_deploy_contracts.js | 7 +
test/TestSupplyChain.sol | 27 +++
test/ast-helper.js | 47 +++++
test/exceptionsHelpers.js | 22 ++
test/supply_chain.test.js | 340 ++++++++++++++++++++++++++++++
truffle-config.js | 9 +
12 files changed, 779 insertions(+)
create mode 100644 .github/workflows/node.js.yml
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 contracts/Migrations.sol
create mode 100644 contracts/SupplyChain.sol
create mode 100644 migrations/1_initial_migration.js
create mode 100644 migrations/2_deploy_contracts.js
create mode 100644 test/TestSupplyChain.sol
create mode 100644 test/ast-helper.js
create mode 100644 test/exceptionsHelpers.js
create mode 100644 test/supply_chain.test.js
create mode 100644 truffle-config.js
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
new file mode 100644
index 0000000..8bdbf0e
--- /dev/null
+++ b/.github/workflows/node.js.yml
@@ -0,0 +1,47 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+ push:
+ branches:
+ - master
+ - final-updates
+ pull_request:
+ branches:
+ -master
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [12.x]
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install latest truffle
+ run: npm -g install truffle
+
+ - name: Setup for Consensys Academy
+ if: ${{ github.repository_owner == 'Consensys-Academy' }}
+ run: git checkout cea0c82a1a36991ea5943f8e315af30dde903b89 test/supply_chain.test.js
+
+ - name: Setup for student grading
+ if: ${{ github.repository_owner != 'Consensys-Academy' }}
+ # get initial commit. this will be the first commit for repos created from template
+ run: git checkout $(git rev-list --max-parents=0 master) test/supply_chain.test.js
+
+ - name: Run tests
+ run: truffle test test/supply_chain.test.js
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e3fbd98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+build
+node_modules
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2a2ca00
--- /dev/null
+++ b/README.md
@@ -0,0 +1,121 @@
+# Supply Chain Exercise
+
+The Supply Chain directory is a truffle project that contains the required
+contract, migration and test files. In this exercise you are going to implement
+the SupplyChain.sol contract and write some tests in Solidity.
+
+Clone this repo to your local machine.
+
+Follow the comments outlined in SupplyChain.sol (in the contracts directory) to
+implement its functions. We have written a set of tests (in javascript) to
+determine if your implementation is correct. As an additional challenge, try
+writing some Solidity tests in TestSupplyChain.sol.
+
+To test your implementation run `$ truffle test` from the terminal in the
+project directory. There are **23 pending tests** that you must pass to complete
+this exercise.
+
+## Instructions
+
+Check out the test file to see the tests that define the behavior of the
+SupplyChain smart contract. Notice the tests are in `it` blocks and have a
+`skip` modifier, which disables the test. To enable the test, remove the
+`.skip` modifier. Tests can have two modifiers: `skip` which skips the test,
+and `only` which runs only that test. But what if more than one test have the
+`only` modifier you may ask? Well only those test marked such will be executed.
+
+### State variables
+
+ - [ ] should have an owner
+ :book:
+
+ The contract should have an owner, of type address that is public.
+ **hint:** define a public variable `owner` of type address
+
+
+
+ - [ ] should have an skuCount
+ :book:
+
+ The contract will keep track of the
+ [sku](https://en.wikipedia.org/wiki/Stock_keeping_unit)s in our supply
+ chain. Each item for sale will have a unique sku number.
+
+ **hint**: define a public variable called `skuCounter` of type uint
+
+
+
+### enum State
+
+Items can exist in our Supply chain domain in a few states. In Solidity an
+`enum` can be used to represent these different states. Remove the `skip`
+annotation from the enum tests to proceed.
+
+ - [ ] should define `ForSale` for when an item is put on sale
+ - [ ] should define `Sold` for when an item has been purchased
+ - [ ] should define `Shipped` for when an item has been shippd to the buyer
+ - [ ] should define `Received` for when the shipped item has been received by the buyer
+
+### Item struct
+
+How do we describe an item in our supply chain? It is a union of properties:
+`name`, `sku`, `price`, `state`, `seller` and `buyer`. We can use a Solidity
+`struct` to model this Item. Remove the `skip` annotation from the `Item
+struct` tests and proceed.
+
+ - [ ] should have a `name`
+ - [ ] should have a `sku`
+ - [ ] should have a `price`
+ - [ ] should have a `state`
+ - [ ] should have a `seller`
+ - [ ] should have a `buyer`
+
+### SupplyChain Use cases
+
+**NOTE** Before proceeding, you should un-comment the `fetchItem` function in the contract. This function is necessary to validate the remaining tests.
+
+ - [ ] should add an item with the provided name and price
+ :book:
+ use case: As a seller, I want to add an item for sale. I should
+
+ - [ ] should emit a LogForSale event when an item is added
+ :book:
+ use case: Whenever an item is added (placed for sale), the contract should
+ emit a `LogForSale` event
+
+ - [ ] should allow someone to purchase an item and update state accordingly
+ :book:
+ use case: As a buyer, I want to purchase an item that is for sale.
+
+ - [ ] should error when not enough value is sent when purchasing an item
+ :book:
+ use case: A buyer will be notified when they do not have enough funds for the purchase
+
+ - [ ] should emit LogSold event when and item is purchased
+ :book:
+ use case: Whenever an item is bought (sold), the contract should emit a "LogSold" event
+
+ - [ ] should revert when someone that is not the seller tries to call shipItem()
+ :book:
+ use case: As a seller, only I can ship a bought item
+
+ - [ ] should allow the seller to mark the item as shipped
+ :book:
+ use case : Whenever an item is shipped, the seller should be able to mark the item as shipped
+
+ - [ ] should emit a LogShipped event when an item is shipped
+ :book:
+ use case: Whenever the item is shipped, the contract should emit a "LogShipped" event
+
+ - [ ] should allow the buyer to mark the item as received
+ :book:
+ use case: Whenever an item is recieved, the buyer should be able to mark the item as received
+
+ - [ ] should revert if an address other than the buyer calls receiveItem()
+ :book:
+ use case: As a buyer, only I can mark the item as received
+
+ - [ ] should emit a LogReceived event when an item is received
+ :book:
+ use case: Whenever an item is received, the contract should emit a "LogReceived" event
+
diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol
new file mode 100644
index 0000000..ef9d595
--- /dev/null
+++ b/contracts/Migrations.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.5.16 <0.9.0;
+
+contract Migrations {
+ address public owner = msg.sender;
+ uint public last_completed_migration;
+
+ modifier restricted() {
+ require(
+ msg.sender == owner,
+ "This function is restricted to the contract's owner"
+ );
+ _;
+ }
+
+ function setCompleted(uint completed) public restricted {
+ last_completed_migration = completed;
+ }
+}
diff --git a/contracts/SupplyChain.sol b/contracts/SupplyChain.sol
new file mode 100644
index 0000000..1092fd9
--- /dev/null
+++ b/contracts/SupplyChain.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.5.16 <0.9.0;
+
+contract SupplyChain {
+
+ //
+
+ //
+
+ //
+
+ //
+
+ //
+
+ /*
+ * Events
+ */
+
+ //
+
+ //
+
+ //
+
+ //
+
+
+ /*
+ * Modifiers
+ */
+
+ // Create a modifer, `isOwner` that checks if the msg.sender is the owner of the contract
+
+ // = _price);
+ _;
+ }
+
+ modifier checkValue(uint _sku) {
+ //refund them after pay for item (why it is before, _ checks for logic before func)
+ _;
+ // uint _price = items[_sku].price;
+ // uint amountToRefund = msg.value - _price;
+ // items[_sku].buyer.transfer(amountToRefund);
+ }
+
+ // For each of the following modifiers, use what you learned about modifiers
+ // to give them functionality. For example, the forSale modifier should
+ // require that the item with the given sku has the state ForSale. Note that
+ // the uninitialized Item.State is 0, which is also the index of the ForSale
+ // value, so checking that Item.State == ForSale is not sufficient to check
+ // that an Item is for sale. Hint: What item properties will be non-zero when
+ // an Item has been added?
+
+ // modifier forSale
+ // modifier sold(uint _sku)
+ // modifier shipped(uint _sku)
+ // modifier received(uint _sku)
+
+ constructor() public {
+ // 1. Set the owner to the transaction sender
+ // 2. Initialize the sku count to 0. Question, is this necessary?
+ }
+
+ function addItem(string memory _name, uint _price) public returns (bool) {
+ // 1. Create a new item and put in array
+ // 2. Increment the skuCount by one
+ // 3. Emit the appropriate event
+ // 4. return true
+
+ // hint:
+ // items[skuCount] = Item({
+ // name: _name,
+ // sku: skuCount,
+ // price: _price,
+ // state: State.ForSale,
+ // seller: msg.sender,
+ // buyer: address(0)
+ //});
+ //
+ //skuCount = skuCount + 1;
+ // emit LogForSale(skuCount);
+ // return true;
+ }
+
+ // Implement this buyItem function.
+ // 1. it should be payable in order to receive refunds
+ // 2. this should transfer money to the seller,
+ // 3. set the buyer as the person who called this transaction,
+ // 4. set the state to Sold.
+ // 5. this function should use 3 modifiers to check
+ // - if the item is for sale,
+ // - if the buyer paid enough,
+ // - check the value after the function is called to make
+ // sure the buyer is refunded any excess ether sent.
+ // 6. call the event associated with this function!
+ function buyItem(uint sku) public {}
+
+ // 1. Add modifiers to check:
+ // - the item is sold already
+ // - the person calling this function is the seller.
+ // 2. Change the state of the item to shipped.
+ // 3. call the event associated with this function!
+ function shipItem(uint sku) public {}
+
+ // 1. Add modifiers to check
+ // - the item is shipped already
+ // - the person calling this function is the buyer.
+ // 2. Change the state of the item to received.
+ // 3. Call the event associated with this function!
+ function receiveItem(uint sku) public {}
+
+ // Uncomment the following code block. it is needed to run tests
+ /* function fetchItem(uint _sku) public view */
+ /* returns (string memory name, uint sku, uint price, uint state, address seller, address buyer) */
+ /* { */
+ /* name = items[_sku].name; */
+ /* sku = items[_sku].sku; */
+ /* price = items[_sku].price; */
+ /* state = uint(items[_sku].state); */
+ /* seller = items[_sku].seller; */
+ /* buyer = items[_sku].buyer; */
+ /* return (name, sku, price, state, seller, buyer); */
+ /* } */
+}
diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js
new file mode 100644
index 0000000..4d5f3f9
--- /dev/null
+++ b/migrations/1_initial_migration.js
@@ -0,0 +1,5 @@
+var Migrations = artifacts.require("./Migrations.sol");
+
+module.exports = function(deployer) {
+ deployer.deploy(Migrations);
+};
diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js
new file mode 100644
index 0000000..febfaa5
--- /dev/null
+++ b/migrations/2_deploy_contracts.js
@@ -0,0 +1,7 @@
+//var SimpleBank = artifacts.require("./SimpleBank.sol");
+var SupplyChain = artifacts.require("./SupplyChain.sol");
+
+module.exports = function(deployer) {
+ //deployer.deploy(SimpleBank);
+ deployer.deploy(SupplyChain);
+};
diff --git a/test/TestSupplyChain.sol b/test/TestSupplyChain.sol
new file mode 100644
index 0000000..c3008cf
--- /dev/null
+++ b/test/TestSupplyChain.sol
@@ -0,0 +1,27 @@
+pragma solidity ^0.5.0;
+
+import "truffle/Assert.sol";
+import "truffle/DeployedAddresses.sol";
+import "../contracts/SupplyChain.sol";
+
+contract TestSupplyChain {
+
+ // Test for failing conditions in this contracts:
+ // https://truffleframework.com/tutorials/testing-for-throws-in-solidity-tests
+
+ // buyItem
+
+ // test for failure if user does not send enough funds
+ // test for purchasing an item that is not for Sale
+
+ // shipItem
+
+ // test for calls that are made by not the seller
+ // test for trying to ship an item that is not marked Sold
+
+ // receiveItem
+
+ // test calling the function from an address that is not the buyer
+ // test calling the function on an item not marked Shipped
+
+}
diff --git a/test/ast-helper.js b/test/ast-helper.js
new file mode 100644
index 0000000..9ccee79
--- /dev/null
+++ b/test/ast-helper.js
@@ -0,0 +1,47 @@
+// Novel way to drive behavior of Smart Contract.
+
+//
+const CDTYPE = "ContractDefinition";
+const CNAME = "SupplyChain";
+const contractDefn = ca =>
+ ca.ast.nodes.find(n => n.nodeType === CDTYPE && n.name === CNAME);
+
+const items = (ca) => {
+ const item = contractDefn(ca).nodes.find((n) => n.name === "Item");
+ if (!item) return null;
+
+ return item
+ .members
+ .map((t) => ({
+ name: t.name,
+ nodeType: t.nodeType,
+ stateVariable: t.stateVariable,
+ type: t.typeName.name,
+ mutability: t.typeName.stateMutability,
+ }));
+};
+
+const isDefined = members => variableName => {
+ return members
+ ? members.find((item) => item.name === variableName)
+ : null;
+};
+
+const isPayable = members => variableName => {
+ if (members === undefined) return false;
+ const definition = members.find((item) => item.name === variableName);
+ return definition && definition.mutability === "payable";
+};
+
+const isType = members => variableName => type => {
+ if (members === undefined) return false;
+ const definition = members.find((item) => item.name === variableName);
+ return definition && definition.type === type;
+};
+
+module.exports = {
+ items,
+ isDefined,
+ isPayable,
+ isType,
+};
diff --git a/test/exceptionsHelpers.js b/test/exceptionsHelpers.js
new file mode 100644
index 0000000..fde8c5d
--- /dev/null
+++ b/test/exceptionsHelpers.js
@@ -0,0 +1,22 @@
+const errorString = "VM Exception while processing transaction: ";
+
+async function tryCatch(promise, reason) {
+ try {
+ await promise;
+ throw null;
+ }
+ catch (error) {
+ assert(error, "Expected a VM exception but did not get one");
+ assert(error.message.search(errorString + reason) >= 0, "Expected an error containing '" + errorString + reason + "' but got '" + error.message + "' instead");
+ }
+};
+
+module.exports = {
+ catchRevert : async function(promise) {await tryCatch(promise, "revert" );},
+ catchOutOfGas : async function(promise) {await tryCatch(promise, "out of gas" );},
+ catchInvalidJump : async function(promise) {await tryCatch(promise, "invalid JUMP" );},
+ catchInvalidOpcode : async function(promise) {await tryCatch(promise, "invalid opcode" );},
+ catchStackOverflow : async function(promise) {await tryCatch(promise, "stack overflow" );},
+ catchStackUnderflow : async function(promise) {await tryCatch(promise, "stack underflow" );},
+ catchStaticStateChange : async function(promise) {await tryCatch(promise, "static state change");},
+};
diff --git a/test/supply_chain.test.js b/test/supply_chain.test.js
new file mode 100644
index 0000000..208cdcb
--- /dev/null
+++ b/test/supply_chain.test.js
@@ -0,0 +1,340 @@
+let BN = web3.utils.BN;
+let SupplyChain = artifacts.require("SupplyChain");
+let { catchRevert } = require("./exceptionsHelpers.js");
+const { items: ItemStruct, isDefined, isPayable, isType } = require("./ast-helper");
+
+contract("SupplyChain", function (accounts) {
+ const [_owner, alice, bob] = accounts;
+ const emptyAddress = "0x0000000000000000000000000000000000000000";
+
+ const price = "1000";
+ const excessAmount = "2000";
+ const name = "book";
+
+ let instance;
+
+ beforeEach(async () => {
+ instance = await SupplyChain.new();
+ });
+
+ describe("Variables", () => {
+ it("should have an owner", async () => {
+ assert.equal(typeof instance.owner, 'function', "the contract has no owner");
+ });
+
+ it("should have an skuCount", async () => {
+ assert.equal(typeof instance.skuCount, 'function', "the contract has no skuCount");
+ });
+
+ describe("enum State", () => {
+ let enumState;
+ before(() => {
+ enumState = SupplyChain.enums.State;
+ assert(
+ enumState,
+ "The contract should define an Enum called State"
+ );
+ });
+
+ it("should define `ForSale`", () => {
+ assert(
+ enumState.hasOwnProperty('ForSale'),
+ "The enum does not have a `ForSale` value"
+ );
+ });
+
+ it("should define `Sold`", () => {
+ assert(
+ enumState.hasOwnProperty('Sold'),
+ "The enum does not have a `Sold` value"
+ );
+ });
+
+ it("should define `Shipped`", () => {
+ assert(
+ enumState.hasOwnProperty('Shipped'),
+ "The enum does not have a `Shipped` value"
+ );
+ });
+
+ it("should define `Received`", () => {
+ assert(
+ enumState.hasOwnProperty('Received'),
+ "The enum does not have a `Received` value"
+ );
+ });
+ })
+
+ describe("Item struct", () => {
+ let subjectStruct;
+
+ before(() => {
+ subjectStruct = ItemStruct(SupplyChain);
+ assert(
+ subjectStruct !== null,
+ "The contract should define an `Item Struct`"
+ );
+ });
+
+ it("should have a `name`", () => {
+ assert(
+ isDefined(subjectStruct)("name"),
+ "Struct Item should have a `name` member"
+ );
+ assert(
+ isType(subjectStruct)("name")("string"),
+ "`name` should be of type `string`"
+ );
+ });
+
+ it("should have a `sku`", () => {
+ assert(
+ isDefined(subjectStruct)("sku"),
+ "Struct Item should have a `sku` member"
+ );
+ assert(
+ isType(subjectStruct)("sku")("uint"),
+ "`sku` should be of type `uint`"
+ );
+ });
+
+ it("should have a `price`", () => {
+ assert(
+ isDefined(subjectStruct)("price"),
+ "Struct Item should have a `price` member"
+ );
+ assert(
+ isType(subjectStruct)("price")("uint"),
+ "`price` should be of type `uint`"
+ );
+ });
+
+ it("should have a `state`", () => {
+ assert(
+ isDefined(subjectStruct)("state"),
+ "Struct Item should have a `state` member"
+ );
+ assert(
+ isType(subjectStruct)("state")("State"),
+ "`state` should be of type `State`"
+ );
+ });
+
+ it("should have a `seller`", () => {
+ assert(
+ isDefined(subjectStruct)("seller"),
+ "Struct Item should have a `seller` member"
+ );
+ assert(
+ isType(subjectStruct)("seller")("address"),
+ "`seller` should be of type `address`"
+ );
+ assert(
+ isPayable(subjectStruct)("seller"),
+ "`seller` should be payable"
+ );
+ });
+
+ it("should have a `buyer`", () => {
+ assert(
+ isDefined(subjectStruct)("buyer"),
+ "Struct Item should have a `buyer` member"
+ );
+ assert(
+ isType(subjectStruct)("buyer")("address"),
+ "`buyer` should be of type `address`"
+ );
+ assert(
+ isPayable(subjectStruct)("buyer"),
+ "`buyer` should be payable"
+ );
+ });
+ });
+ });
+
+ describe("Use cases", () => {
+ it("should add an item with the provided name and price", async () => {
+ await instance.addItem(name, price, { from: alice });
+
+ const result = await instance.fetchItem.call(0);
+
+ assert.equal(
+ result[0],
+ name,
+ "the name of the last added item does not match the expected value",
+ );
+ assert.equal(
+ result[2].toString(10),
+ price,
+ "the price of the last added item does not match the expected value",
+ );
+ assert.equal(
+ result[3].toString(10),
+ SupplyChain.State.ForSale,
+ 'the state of the item should be "For Sale"',
+ );
+ assert.equal(
+ result[4],
+ alice,
+ "the address adding the item should be listed as the seller",
+ );
+ assert.equal(
+ result[5],
+ emptyAddress,
+ "the buyer address should be set to 0 when an item is added",
+ );
+ });
+
+ it("should emit a LogForSale event when an item is added", async () => {
+ let eventEmitted = false;
+ const tx = await instance.addItem(name, price, { from: alice });
+
+ if (tx.logs[0].event == "LogForSale") {
+ eventEmitted = true;
+ }
+
+ assert.equal(
+ eventEmitted,
+ true,
+ "adding an item should emit a For Sale event",
+ );
+ });
+
+ it("should allow someone to purchase an item and update state accordingly", async () => {
+ await instance.addItem(name, price, { from: alice });
+ var aliceBalanceBefore = await web3.eth.getBalance(alice);
+ var bobBalanceBefore = await web3.eth.getBalance(bob);
+
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+
+ var aliceBalanceAfter = await web3.eth.getBalance(alice);
+ var bobBalanceAfter = await web3.eth.getBalance(bob);
+
+ const result = await instance.fetchItem.call(0);
+
+ assert.equal(
+ result[3].toString(10),
+ SupplyChain.State.Sold,
+ 'the state of the item should be "Sold"',
+ );
+
+ assert.equal(
+ result[5],
+ bob,
+ "the buyer address should be set bob when he purchases an item",
+ );
+
+ assert.equal(
+ new BN(aliceBalanceAfter).toString(),
+ new BN(aliceBalanceBefore).add(new BN(price)).toString(),
+ "alice's balance should be increased by the price of the item",
+ );
+
+ assert.isBelow(
+ Number(bobBalanceAfter),
+ Number(new BN(bobBalanceBefore).sub(new BN(price))),
+ "bob's balance should be reduced by more than the price of the item (including gas costs)",
+ );
+ });
+
+ it("should error when not enough value is sent when purchasing an item", async () => {
+ await instance.addItem(name, price, { from: alice });
+ await catchRevert(instance.buyItem(0, { from: bob, value: 1 }));
+ });
+
+ it("should emit LogSold event when and item is purchased", async () => {
+ var eventEmitted = false;
+
+ await instance.addItem(name, price, { from: alice });
+ const tx = await instance.buyItem(0, { from: bob, value: excessAmount });
+
+ if (tx.logs[0].event == "LogSold") {
+ eventEmitted = true;
+ }
+
+ assert.equal(eventEmitted, true, "adding an item should emit a Sold event");
+ });
+
+ it("should revert when someone that is not the seller tries to call shipItem()", async () => {
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: price });
+ await catchRevert(instance.shipItem(0, { from: bob }));
+ });
+
+ it("should allow the seller to mark the item as shipped", async () => {
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+ await instance.shipItem(0, { from: alice });
+
+ const result = await instance.fetchItem.call(0);
+
+ assert.equal(
+ result[3].toString(10),
+ SupplyChain.State.Shipped,
+ 'the state of the item should be "Shipped"',
+ );
+ });
+
+ it("should emit a LogShipped event when an item is shipped", async () => {
+ var eventEmitted = false;
+
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+ const tx = await instance.shipItem(0, { from: alice });
+
+ if (tx.logs[0].event == "LogShipped") {
+ eventEmitted = true;
+ }
+
+ assert.equal(
+ eventEmitted,
+ true,
+ "adding an item should emit a Shipped event",
+ );
+ });
+
+ it("should allow the buyer to mark the item as received", async () => {
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+ await instance.shipItem(0, { from: alice });
+ await instance.receiveItem(0, { from: bob });
+
+ const result = await instance.fetchItem.call(0);
+
+ assert.equal(
+ result[3].toString(10),
+ SupplyChain.State.Received,
+ 'the state of the item should be "Received"',
+ );
+ });
+
+ it("should revert if an address other than the buyer calls receiveItem()", async () => {
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+ await instance.shipItem(0, { from: alice });
+
+ await catchRevert(instance.receiveItem(0, { from: alice }));
+ });
+
+ it("should emit a LogReceived event when an item is received", async () => {
+ var eventEmitted = false;
+
+ await instance.addItem(name, price, { from: alice });
+ await instance.buyItem(0, { from: bob, value: excessAmount });
+ await instance.shipItem(0, { from: alice });
+ const tx = await instance.receiveItem(0, { from: bob });
+
+ if (tx.logs[0].event == "LogReceived") {
+ eventEmitted = true;
+ }
+
+ assert.equal(
+ eventEmitted,
+ true,
+ "adding an item should emit a Shipped event",
+ );
+ });
+
+ });
+
+});
diff --git a/truffle-config.js b/truffle-config.js
new file mode 100644
index 0000000..e5af9e5
--- /dev/null
+++ b/truffle-config.js
@@ -0,0 +1,9 @@
+module.exports = {
+ networks: {
+ local: {
+ host: "localhost",
+ port: 8545,
+ network_id: "*" // Match any network id
+ }
+ }
+};