Supply Chain Management (Life-Saving Drugs) Through Blockchain: A Case Study

Supply Chain Management (Life-Saving Drugs) Through Blockchain: A Case Study

A case study on how blockchain can help overcome challenges in supply chain management of critical drugs.

Ruchika Gupta's photo
Ruchika Gupta
·May 26, 2022·

12 min read

Play this article

Table of contents

Blockchain has been the game-changing technology behind cryptocurrencies and everything Decentralised Finance (DeFi). However, the underlying principles of how blockchain works are being leveraged across other sectors these days. One such sector where it holds great value is supply chain management. Blockchain has the potential of improving supply chains by enabling faster and more cost-efficient products delivery, enhancing products traceability, and improving coordination between partners.

Companies such as Walmart, Emerson, Hayward, IBM, and MasterCard operate in varied industries such as retailing, technology, and financial services. They are either conducting pilots or have already moved even further and are working with supply chain partners to develop real-time applications. In this case study we will be creating a supply chain management platform which deals with life saving drugs. In case you need more information on the project's code base, find the same here.

Why Blockchain based supply chain management?

Since the 1990's, there has been a considerable advancement in supply chain information sharing, thanks to the use of enterprise resource planning (ERP) systems. However, visibility remains a challenge in large supply chains that involve complex transactions.

Let us consider a scenario of a simple transaction involving a manufacturer that sources a product to a supplier and a vendor that sources the product from supplier to later sell it to customers. However, in the flow from manufacturer to supplier to vendor, the financial institutions which are key to financing this chain are in a blind spot.

The transaction involves information, inventory, and financial flows. The state-of-the-art ERP systems, manual audits, and inspections cannot connect all these flows reliably. This makes it hard to eliminate execution errors, improve decision-making, and resolve supply chain conflicts. Blockchain can help overcome some of these limitations and create a seamless supply chain management experience for users.

The following diagram depicts the difference between conventional record keeping in supply chain management against blockchain.

Screenshot 2022-04-12 at 12.17.20 PM.png

Case Study

During the pandemic, countries around the world faced shortages, interruption, and illegal hoarding of critical life-saving drugs. Sighting this problem, we will be creating an effective supply chain management solution for critical life-saving drugs in this case study. We will be writing a smart contract that brings manufacturers, suppliers, vendors, and customers on a single platform to improve decision-making and resolve supply chain conflicts.

Tech Stack

  • Ethereum blockchain for smart contract.
  • ReactJS website for a common portal for interacting with the contract.

Business Flow

Typically in a supply chain management flow we have 4 key entities i.e., a manufacturer, a supplier, a vendor, and in the end of the chain are customers. The following flow diagram illustrates how the supply chain management would be: supply-chain-flow-diagram.jpg

Manufacturers

  • Manufacturers can add the products to Inventory.
  • Manufacturers will be further adding Suppliers and selling the products directly to them.
  • Each Manufacturer can have many Suppliers.
  • Manufacturers are essentially the creators of the contract.

Suppliers

  • Suppliers will add Vendors under them.
  • Each Supplier can have many Vendors.
  • Suppliers will be selling the products to Vendors.

Vendors

  • Vendors can add Customers.
  • Vendors will be selling the product to Customers.

Product ownership

Based on the various entities we discussed above, the product ownership will flow from: Manufacturer-> Supplier-> Vendor-> Customer

product-ownership-transfer.jpg

Assumptions

  • All user entities must have mandatory blockchain address on the network to buy and sell products.
  • Product details and supply chain lifecycle is public to all type of users on the network.
  • For each entity such as Manufacturer or Supplier, we are limited to one access. However contract can be modified to support multiple access for each entity as well.

Smart contract for supply chain management

Now that our business flow is ready, let's get started with the smart contract.

Users and roles mapping.

A user on the contract will be identified by their address and role type. We created a library to define common types used.

library Types {
    enum UserRole {
        Manufacturer, // 0
        Supplier, // 1
        Vendor, // 2
        Customer // 3
    }

    struct User {
        UserRole role;
        address id_;
        // Further attributes can be added for an entity such as name, email, address ...
    }
}

Product

Within the types library we have a struct for product, defining all it's attributes.

struct Product {
        string name;
        string manufacturerName;
        address manufacturer;
        uint256 manDateEpoch;
        uint256 expDateEpoch;
        bool isInBatch; // few products will be packed & sold in batches
        uint256 batchCount; // QTY that were packed in single batch
        string barcodeId;
        string productImage;
        ProductType productType;
        // Further attributes goes here.....
    }

Product user relation.

In supply chain, each product will move to a different entity layer. Hence, we need to keep track of its history across each stage. We will be creating a struct for product and entity relationship and later storing the product's history in the supply chain against the product ID as key.

struct ProductHistory {
        UserHistory manufacturer;
        UserHistory supplier;
        UserHistory vendor;
        UserHistory[] customers;
    }

User specific contract

Now that our types are set, let's write all the crud required at the user layer in a contract named Users. These include adding a user as Manufacturer, Supplier, and so on, updating user, deleting the user, as well as getting an information specific to a user entity.

contract Users {
    mapping(address => Types.UserDetails) internal users; // contains all users.
    mapping(address => Types.UserDetails[]) internal manufacturerSuppliersList; // manufacturer -> suppliers list
    mapping(address => Types.UserDetails[]) internal supplierVendorsList; // supplier -> vendors list
    mapping(address => Types.UserDetails[]) internal vendorCustomersList; // vendor -> customers list
}

Add a user

The following method adds a user entity based on its type. Every time a user is added, we emit an event NewUser on the blockchain, which can be listened by client to update the view while front-end integration is being done.

/**
     * @dev To add a particular user to a particular role
     * @param user UserDetails that need to be added
     */
    function add(Types.UserDetails memory user) internal {
        require(user.id_ != address(0));
        require(!has(user.role, user.id_), "Same user with same role exists");
        users[user.id_] = user;
        emit NewUser(user.name, user.email, user.role);
    }

    /**
     * @dev To add a particular user to a current logged-in user's correspondence list
     * @param user UserDetails that need to be added
     * @param myAccount User address who is trying to add the other user
     */
    function addparty(Types.UserDetails memory user, address myAccount)
        internal
    {
        require(myAccount != address(0));
        require(user.id_ != address(0));

        if (
            get(myAccount).role == Types.UserRole.Manufacturer &&
            user.role == Types.UserRole.Supplier
        ) {
            // Only manufacturers are allowed to add suppliers
            manufacturerSuppliersList[myAccount].push(user);
            add(user); // To add user to global list
        } else if (
            get(myAccount).role == Types.UserRole.Supplier &&
            user.role == Types.UserRole.Vendor
        ) {
            // Only suppliers are allowed to add vendors
            supplierVendorsList[myAccount].push(user);
            add(user); // To add user to global list
        } else if (
            get(myAccount).role == Types.UserRole.Vendor &&
            user.role == Types.UserRole.Customer
        ) {
            // Only vendors are allowed to add customers
            vendorCustomersList[myAccount].push(user);
            add(user); // To add user to global list
        } else {
            revert("Not valid operation");
        }
    }

Get user details

/**
     * @dev To get details of the user
     * @param id_ User Id for whom the details were needed
     * @return user_ Details of the current logged-in User
     */
    function getPartyDetails(address id_)
        internal
        view
        returns (Types.UserDetails memory)
    {
        require(id_ != address(0));
        require(get(id_).id_ != address(0));
        return get(id_);
    }

    /**
     * @dev To get user details based on the address
     * @param account User address that need to be linked to user details
     * @return user_ Details of a registered user
     */
    function get(address account)
        internal
        view
        returns (Types.UserDetails memory)
    {
        require(account != address(0));
        return users[account];
    }

Remove a user

/**
     * @dev To remove a particular user from a particular role
     * @param role User role for which he/she has to be dismissed for
     * @param account User Address that need to be removed
     */
    function remove(Types.UserRole role, address account) internal {
        require(account != address(0));
        require(has(role, account));
        string memory name_ = users[account].name;
        string memory email_ = users[account].email;
        delete users[account];
        emit lostUser(name_, email_, role);
    }

There are further helper methods in the Users contract, check out the entire contract in details here.

Product specific contract

Product contract has all the methods and state variables associated to all products such as adding a product, getting a product based on product ID, product user ownership details, selling a product down the user hierarchy, and so on.

contract Products {
    Types.Product[] internal products; // list of products added by manufacturer.
    mapping(string => Types.Product) internal product; // product mapped to it's product id.
    mapping(address => string[]) internal userLinkedProducts; // Owner product mapping.
    mapping(string => Types.ProductHistory) internal productHistory; // Product mapping across entities.
}

Add a product

/**
     * @dev Adds new product to the products list
     * @param product_ unique ID of the product
     * @param currentTime_ Current Date, Time in epoch (To store when it got added)
     */
    function addAProduct(Types.Product memory product_, uint256 currentTime_)
        internal
        productNotExists(product_.barcodeId)
    {
        require(
            product_.manufacturer == msg.sender,
            "Only manufacturer can add"
        );
        products.push(product_);
        product[product_.barcodeId] = product_;
        productHistory[product_.barcodeId].manufacturer = Types.UserHistory({
            id_: msg.sender,
            date: currentTime_
        });
        userLinkedProducts[msg.sender].push(product_.barcodeId);
        emit NewProduct(
            product_.name,
            product_.manufacturerName,
            product_.scientificName,
            product_.barcodeId,
            product_.manDateEpoch,
            product_.expDateEpoch
        );
    }

Sell a product

While the product is sold down the user hierarchy, its ownership gets transferred accordingly. Further relevant events are emitted at each step of the product lifecycle.

/**
     * @dev Transfer the ownership
     * @param partyId_ Purchase user's account address
     * @param barcodeId_ unique ID of the product
     * @param currentTime_ Current Date Time in epoch to keep track of the history
     */
    function sell(
        address partyId_,
        string memory barcodeId_,
        Types.UserDetails memory party_,
        uint256 currentTime_
    ) internal productExists(barcodeId_) {
        Types.Product memory product_ = product[barcodeId_];

        // Updating product history
        Types.UserHistory memory userHistory_ = Types.UserHistory({
            id_: party_.id_,
            date: currentTime_
        });
        if (Types.UserRole(party_.role) == Types.UserRole.Supplier) {
            productHistory[barcodeId_].supplier = userHistory_;
        } else if (Types.UserRole(party_.role) == Types.UserRole.Vendor) {
            productHistory[barcodeId_].vendor = userHistory_;
        } else if (Types.UserRole(party_.role) == Types.UserRole.Customer) {
            productHistory[barcodeId_].customers.push(userHistory_);
        } else {
            // Not in the assumption scope
            revert("Not valid operation");
        }
        transferOwnership(msg.sender, partyId_, barcodeId_); // To transfer ownership from seller to buyer

        // Emiting event
        emit ProductOwnershipTransfer(
            product_.name,
            product_.manufacturerName,
            product_.scientificName,
            product_.barcodeId,
            party_.name,
            party_.email
        );
    }

Transferring product ownership

The function to transfer ownership of the product is an internal function that is to be used while the product is being sold.


    /**
     * @dev To remove the product from current list once sold
     * @param sellerId_ Seller's metamask address
     * @param buyerId_ Buyer's metamask address
     * @param productId_ unique ID of the product
     */
    function transferOwnership(
        address sellerId_,
        address buyerId_,
        string memory productId_
    ) internal {
        userLinkedProducts[buyerId_].push(productId_);
        string[] memory sellerProducts_ = userLinkedProducts[sellerId_];
        uint256 matchIndex_ = (sellerProducts_.length + 1);
        for (uint256 i = 0; i < sellerProducts_.length; i++) {
            if (compareStrings(sellerProducts_[i], productId_)) {
                matchIndex_ = i;
                break;
            }
        }
        assert(matchIndex_ < sellerProducts_.length); // Match found
        if (sellerProducts_.length == 1) {
            delete userLinkedProducts[sellerId_];
        } else {
            userLinkedProducts[sellerId_][matchIndex_] = userLinkedProducts[
                sellerId_
            ][sellerProducts_.length - 1];
            delete userLinkedProducts[sellerId_][sellerProducts_.length - 1];
            userLinkedProducts[sellerId_].pop();
        }
    }

There are further helper methods, function, and modifiers in the Products contract. Check out the entire contract in details here.

SupplyChain contract

Now that we have created separate contracts for products and users to handle all the required crud operations, we will be creating our main contract SupplyChain which will in turn inherit Users and Products contract. The constructor method sets the creator of the contract as the Manufacturer. The contract consist of all the methods required for our supply chain management flow.

// SPDX-License-Identifier: GPL-3.0
pragma experimental ABIEncoderV2;
pragma solidity >=0.4.25 <0.9.0;

import "./Products.sol";
import "./Users.sol";

/**
 * @title SupplyChain
 * @author Suresh Konakanchi | GeekyAnts
 * @dev Implements the transparency in the supply chain to
 * understand the actual user & the flow of the products/medicines
 */
contract SupplyChain is Users, Products {
    /**
     * @dev Create a new SupplyChain with the provided 'manufacturer'
     * @param name_ Name of the manufacturer
     * @param email_ Email of the manufacturer
     */
    constructor(string memory name_, string memory email_) {
        Types.UserDetails memory mn_ = Types.UserDetails({
            role: Types.UserRole.Manufacturer,
            id_: msg.sender,
            name: name_,
            email: email_
        });
        add(mn_);
    }

Some of the key methods in the supply chain contract are as follows:

Add product

/**
     * @dev Adds new product to the products list
     * @param product_ unique ID of the product
     * @param currentTime_ Current Date, Time in epoch (To store when it got added)
     */
    function addProduct(Types.Product memory product_, uint256 currentTime_)
        public
        onlyManufacturer
    {
        addAProduct(product_, currentTime_);
    }

Sell product

/**
     * @dev Transfer the ownership
     * @param partyId_ Purchase user's account address
     * @param barcodeId_ unique ID of the product
     * @param currentTime_ Current Date Time in epoch to keep track of the history
     */
    function sellProduct(
        address partyId_,
        string memory barcodeId_,
        uint256 currentTime_
    ) public {
        require(isPartyExists(partyId_), "Party not found");
        Types.UserDetails memory party_ = users[partyId_];
        sell(partyId_, barcodeId_, party_, currentTime_);
    }

Add a party

/**
     * @dev To add an user to my account, which can be used in future at the time of selling
     * @param user_ Details of the user that need to be added
     */
    function addParty(Types.UserDetails memory user_) public {
        addparty(user_, msg.sender);
    }

The SupplyChain contracts consist of functions as public, internal, and external in order to facilitate data required both at the contract level as well as one for front-end. Check out the entire contract in details here.

Advantages of supply chain management through blockchain

  • Increasing efficiency, speed, and reduced disruptions.

  • Any counterfeit in the process can be traced to its source using the blockchain trail.

  • Members of a supply chain can always ascertain the source and quality of their inventory since each unit of the supply chain is firmly coupled with the identity of its particular owner at every step along the way.

Dis-advantages of supply chain management through blockchain

  • Even though blockchain record is secure, there is still the risk of a contaminated or counterfeit product being tagged and introduced into the supply chain, either in error or by a corrupt actor.

  • Inaccurate inventory data resulting from mistakes in scanning, tagging, and data entry, since flow is prone to errors.

  • Transactions on a blockchain network are accepted by the majority of participants (proof of work), but unfortunately, it also limits the speed at which new blocks can be added. Consequently, it is too slow to handle the speed and volume of transactions in supply chains.

Conclusion

Blockchain improves supply chains in terms of end-to-end traceability, speed of product delivery, level and quality of coordination, and eliminating counterfeit. It's time for supply chain managers across industry sectors to explore and conduct pilots with various blockchain platforms and build an ecosystem with other firms. The investment will definitely generate handsome returns.

You can find the full source code here

What next?

We started with blockchain, the underlying mechanism of how it works, and wrote our first smart contract and a case study around electronic voting using blockchain. You can also checkout our E-Voting Case Study. In the next article, we will go through the case study of blockchain in KYC Chain.

This article is part of Research & Development work being done by Pushkar Kumar, Suresh konakanchi, and Ruchika Gupta. We will be covering a series of articles along with open source projects around blockchain, smart contracts, and web3 in general. Here is the list of all the articles that you can follow to start with Blockchain and write your first contract.

 
Share this