Building a Smart Contract for Logistics Companies
Tags: blockchain, web3
Published: 2022-07-10 08:46:25

Blockchain technology is a revolutionary tool that can be applied to many industries. You have already seen how blockchain can be used as a secure ledger for digital currency, but it also has many other applications. One such application is in the logistics industry.

The logistics industry has been around for decades and the current system works well for most companies. However, there are still some issues within the industry that need to be addressed. For example, there are times when customer orders outweigh the capacity of the logistic company. They usually resolve this by delaying the time of deliveries which doesn't always turn out to give a good customer experience.

Smart contracts are one way to address these issues. In this article, you will look at how smart contracts can be used in the logistics industry and how they can help improve efficiency and help such a logistic company shed some weight by allowing other logistics companies to plug into their system to deliver the order. You will learn how to build a smart contract by building a system that enables a group of logistics companies within a city to work together as a single unit. Each logistic company would be able to offer delivery assistance to other logistics companies that can't process a delivery quickly. Here, the smart contract acts as the trusted enabler, making the companies exchange delivery data and fees without worry.

In this article, you would do the following:

- Get familiar with Remix IDE

- Define how the smart contract would work

- Build our Smart Contract 

## Remix IDE

In this article, you will use the Remix IDE. The remix is the official Integrated Development Environment for writing Solidity. The remix is similar to most common IDEs like Vscode, Sublime text, and so on. It allows you to quickly build smart contracts, deploy, and test on Ethereum networks without going through extra setups. You can use it directly on your browser at remix.ethereum.org.

### Overview

![overview.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1638707717829/d_8KAmaBJ.jpeg)

The picture above is what you see the first time you visit Remix. Similar to Vscode, the file explorer is on the left side of your screen, the code area on the right, then the terminal/log below the code area.

### File Explorer

![File Explorer.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1638707733602/u2W9mcPXm.jpeg)

This is where you create and manage your files, mostly solidity files (.sol)

### Solidity Compiler

![Compiler.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1638707950829/I-plrBVh7.jpeg)

Once you are done writing your smart contract, you will need to compile it. With Remix, you can easily configure your compiler and compile your solidity script, so it can run on the Ethereum Virtual Machine.

### Deploy and Run Transactions

![deploy and run.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1638708159009/TzdM0DKgt.jpeg)

After successfully compiling your smart contract, you can deploy it on a test Ethereum network and run it directly on Remix IDE.

## How will this smart contract work?

- The smart contract would be launched by an account into an **Ethereum network** through a **transaction**. This account is the contract **initiator**. The transaction would include some **ether** (Ethereum cryptocurrency) which the initiator will use to register itself as a member of the smart contract.

- Each of the logistic companies would create an account on the Ethereum network. These accounts would be used to **register as a member** on the smart contract. 

- During registration, a minimum amount of ether (e.g 10) is set as the **registration fee**. This fee serves as a requirement for becoming a member of the Logistics smart contract (PS - requirements can vary in real-life applications).

- After registration into the smart contract, members are given a **reputation** number (e.g 10). A member can request **delivery assistance** from the smart contract. This member is the **delivery owner**. During this request, an ether amount, equivalent to the **delivery fee** is sent alongside a **hashed** (encrypted) data of the delivery.

- Any member of the smart contract who is **interested in assisting** in the delivery would respond to the **delivery request**, **decrypt** the delivery details, then use the details to complete the delivery process. This member is referred to as the **assisting member** of the delivery.

- After the delivery is completed, the **assisting member** marks the delivery as **completed** on the smart contract. The **delivery owner** will **confirm the completeness** of the delivery (*This can happen after the customer has indicated that the delivery has been completed*).

- After the delivery has been confirmed, the **delivery fee** is sent from the **delivery owner's ** balance to the **assisting member** balance on the smart contract. The **reputation** of the **assisting member** would be **increased** by some values (e.g 3).

- If the delivery **failed** or was **canceled** by the **assisting member**, its **reputation** would be **reduced** by some value (e.g 5).

- The **reputation** of every member on the smart contract is important as it serves as a factor to **validate the authenticity** of each member ( a logistics company). 


## Build your Smart Contract

A smart contract is made up of **data (states)**, **functions** to access or change the data, and **rules (modifiers)** that act as gatekeepers for the data and the functions. 

It is a best practice to analyze a smart contract before writing any line of code. Analyzing a smart contract gives clarity on what we want to build. 

The diagram below shows an overview of our smart contract.

![smart contract.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1638709893724/-CX0lATGg.jpeg)

Your smart contract would be made up of four important components. 
- Name
- State data
- Rules or modifiers
- Functions

### Name

This is simply the name of your contract. It is the first step you need to take in building your smart contract.  Go ahead to your Remix IDE, and create a new file named **Logistics.sol**, then define your smart contract as shown below


```solidity
pragma solidity >=0.4.16 <0.9.0;
contract Logistics {
// our smart contract code goes here
}
``` 
The first line **pragma solidity >=0.4.16 <0.9.0** indicates that the solidity version you are using is between 0.4.16 to 0.8.0. This version specification is needed to correctly compile our solidity code. The **"contract Logistics"** on line 2 defines our smart contract.

### States data
States data are variables (data storage containers) whose values are permanently stored in contract storage. It is always present throughout the existence of the contract. Solidity is a typed language and it has different data types similar to  C++. In this smart contract, you would only use four data types.

- **address **

This is used for storing addresses of contracts or accounts. it is a 160-bit value that does not allow any arithmetic operations.

- **struct**

The struct type is used for storing related data as one.

- **uint**

uint means unsigned integer. it is used for storing integer values

- **mapping**

mapping is quite similar to a javascript array. It is used to store value in a key=>value pair format.

You can check out other data types on [Solidity Documentation](https://docs.soliditylang.org/en/v0.8.10/types.html).

When declaring state data in solidity, the data type is written before the state name. For example,
**address payinguser**:  **address** is the data type, while **payinguser** is the name of the state.

Let's define the state data for our smart contract.

- initiator -> The address of the account that will launch the logistics smart contract

```solidity
    address initiator;
``` 
- memberState -> Holds data for each member registered on the contract. It contains the logistics company's **balance** and **reputation** which are both integers.

```solidity
    struct memberState {
        uint balance;
        uint reputation;
    }
``` 
- deliveryState -> Holds data for each **delivery** that will be sent to the smart contract. It contains **hashed delivery details**, ** delivery fee** in ether, **address ** of the **delivery owner**, the **delivery status** which is an integer that describes the present state of the delivery, and lastly, the **address** of the **assisting member**.

```solidity
// when status = 0, it means pending
// when status = 1, it means in progress
// when status = 2, it means completed
// when status = 3, it means confirmed
   struct deliveryState {
        uint deliveryFee;
        uint status;
        uint hashedDeliveryDetails;
        address owner;
        address assistingMember;
    }
``` 
- membersData -> This maps the account address of every registered member on the smart contract to the memberState, in key => value pair format. The **public** keyword allows the **memberData** to be accessed outside the smart contract by the public.

```solidity
  mapping (address => memberState) public membersData;
``` 
- members -> This maps the account address of every registered member to their state of membership in the smart contract. The membership state is an integer of either 0 or 1 as the value. 0 indicates no membership, while 1 indicates active membership.

```solidity
 mapping (address => uint) members;
``` 
- deliveryData -> This maps the hashed delivery details to a delivery state in the smart contract in a key => value pair format. It is also public and can be accessed outside the smart contract.

```solidity
mapping (uint => deliveryState) public deliveryData;
``` 

Now, add all these data states to your Logistics.sol file. You should have something similar to this:

```solidity
pragma solidity >=0.4.16 <0.9.0;
contract Logistics {

 // The address of the account that launched the smart contract
    address initiator;

    // state data for each member registered on the contract
    struct memberState {
        uint balance;
        uint reputation;
    }

 // state data for each delivery transactions executed through the smart contract
// when status = 0, it means pending
// when status = 1, it means in progress
// when status = 2, it means completed
// when status = 3, it means confirmed
    struct deliveryState {
        uint deliveryFee;
        uint status;
        uint hashedDeliveryDetails;
        address owner;
        address assistingMember;
    }

    // mapping are live arrays with key and value pairs
    mapping (address => memberState) public membersData;
    mapping (address => uint) members;
    mapping (uint => deliveryState) public deliveryData;
}
``` 

### Rules or modifiers

Smart contract rules in solidity are defined using the **modifier** keyword. Modifiers are used for restricting access to data or functions in a smart contract. In this smart contract, you have only one modifier, 

- onlyMember -> This modifier allows only accounts that are registered as a member of the smart contract to access a particular data or function.

```solidity
 modifier onlyMember{
        require(members[msg.sender] == 1);
        _;
    }
```
The **msg**  used in the modifier is a global variable in solidity. It holds the information of a transaction. **msg.sender** gets the address of the account that initiated the transaction, while **msg.value** gets the ether (Ethereum cryptocurrency) sent with the transaction.

Add the modifier to your smart contract:

```solidity
pragma solidity >=0.4.16 <0.9.0;
contract Logistics {

 // The address of the account that launched the smart contract
    address initiator;

    // state data for each member registered on the contract
    struct memberState {
        uint balance;
        uint reputation;
    }

    // state data for each delivery transactions executed through the smart contract
    struct deliveryState {
        uint deliveryFee;
        uint status;
        uint hashedDeliveryDetails;
        address owner;
        address assistingMember;
    }

    // mapping are live arrays with key and value pairs
    mapping (address => memberState) public membersData;
    mapping (address => uint) members;
    mapping (uint => deliveryState) public deliveryData;

 // modifiers
    modifier onlyMember{
        require(members[msg.sender] == 1);
        _;
    }
}
``` 

### Functions

These are the actions and use-cases of the smart contract. You need to perform verification and validations before performing any action. Verification can be achieved by attaching the right modifier to the function, while validations can be achieved by using simple conditional statements. To return meaningful responses during verification and validation, let's add some error handlers to our smart contract.

```solidity
 // errors
    error notAuthorized(string reason);
    error insufficientFee(string reason, uint registrationFee);
```

For your smart contract, you have 7 functions:

- constructor -> This function is common to every smart contract and it is called when launching the contract.

```solidity
 constructor() public payable {
        initiator = msg.sender;
        // here, you will register the initiator of the contract as a member
        members[msg.sender] = 1;
        membersData[msg.sender].balance = msg.value;
        membersData[msg.sender].reputation = 10;
    }
```
The **public** keyword indicates that the function can be called outside the smart contract while the **payable** keyword indicates that the function requires cryptocurrency input.

Here, you set the initiator address and added the account as the first member of the smart contract.

Remember:  **msg.sender** and **msg.value** are both global variables. **msg.sender** returns the address to the account that is calling a function while **msg.value** returns the value of the cryptocurrency attached to the transaction calling the function.

- register -> This function is used to **self-register** an account into the smart contract.

 ```solidity
 function register() public payable {
        uint registrationFee = msg.value;
        address newMember = msg.sender;
        if (registrationFee < 10)
           revert insufficientFee({
                reason: "Ether value is less than the required registration fee",
                registrationFee: 10
            });
        members[newMember] = 1;
        membersData[newMember].balance = msg.value;
        membersData[newMember].reputation = 10;
    }
```
Here, you validated a registration fee of 10 ethers and threw in an error for lesser fees. You also set the membership state, balance, and default reputation for the new member.

- requestForDelivery -> A member calls this function to request for **delivery assistance**. The member must send a **hashed delivery detail** and an equivalent cryptocurrency value when calling the function.

 ```
function requestForDelivery(uint hashedDeliveryDetails) onlyMember public payable {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can request for delivery"
            });

         uint senderReputation = membersData[msg.sender].reputation;
         if(senderReputation < 6)
            revert notAuthorized({
                reason: "You have a low reputation, complete more deliveries to increase your reputations"
            });

        deliveryData[hashedDeliveryDetails].deliveryFee = msg.value;
        deliveryData[hashedDeliveryDetails].status = 0;
        deliveryData[hashedDeliveryDetails].status = hashedDeliveryDetails;
        deliveryData[hashedDeliveryDetails].owner = msg.sender;

        membersData[msg.sender].balance = membersData[msg.sender].balance + msg.value;
    }
```

In the code above, you used the **onlyMember** modifier to verify accounts that can access the function. The payable keyword is an indication that payment in the form of cryptocurrency is required. You checked for the reputation of the member to verify its access to request assistance. You also saved the delivery data and temporarily stored the delivery fee in the wallet of the **delivery owner**. The delivery fee would be sent to the **assisting member** after completing the delivery.

- respondToDeliveryRequest -> Anyone can access the **deliveryData** since it is public. Interested members can respond to the delivery request by using the **hashedDeliveryDetails** to respond and handle the delivery. 

 ```solidity
 function respondToDeliveryRequest(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can respond to delivery request"
            });
        uint memberReputation = membersData[msg.sender].reputation;
         if(memberReputation < 1)
            revert notAuthorized({
                reason: "You have exhausted your reputations"
            });
        uint deliveryStatus = deliveryData[hashedDeliveryDetails].status;
        if(deliveryStatus != 0)
            revert notAuthorized({
                reason: "This delivery is no longer available"
            });

        deliveryData[hashedDeliveryDetails].status = 1;
        deliveryData[hashedDeliveryDetails].assistingMember = msg.sender;
    }
```
Above, you prevented members with no reputations from assisting in any delivery. You also checked the delivery status to prevent multiple delivery assistance, then updated its status and the **assisting member’s** address.

- markDeliveryAsCompleted -> The **assisting member** of delivery calls this function after completing the delivery. 

```solidity
function markDeliveryAsCompleted(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        if(assistingMember != msg.sender) 
            revert notAuthorized({
                reason: "You don't have access to update this delivery"
            });
        deliveryData[hashedDeliveryDetails].status = 2;
    }
```
Here, you ensured the account calling the function has the right access then update the delivery status as completed if it does.

- markDeliveryAsCancled -> This function is used to cancel delivery in progress. Only the delivery **assisting member** and the smart contract **initiator** can access it.

```solidity
function markDeliveryAsCancled(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        if(assistingMember != msg.sender && msg.sender != initiator) 
            revert notAuthorized({
                reason: "You don't have access to update this delivery"
            });
        
        deliveryData[hashedDeliveryDetails].status = 0;

        if (assistingMember == msg.sender) {
             membersData[msg.sender].reputation = membersData[msg.sender].reputation - 5;
        }

        if (msg.sender == initiator) {
             membersData[msg.sender].reputation = membersData[msg.sender].reputation - 6;
        }
    }
```
Here, if the delivery was canceled by the **assisting member**, its reputation drops by 5,  but if it was done by the **initiator**, its reputation will drop by 6.

- markDeliveryAsConfirmed -> This function is used to confirm delivery. It can only be called by the owner of the delivery and the contract initiator who calls it when the owner is not available.

```solidity
function markDeliveryAsConfirmed(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address deliveryOwner = deliveryData[hashedDeliveryDetails].owner;
        if(deliveryOwner != msg.sender && msg.sender != initiator) 
            revert notAuthorized({
                reason: "You don't have access to confirm this delivery"
            });
        
        deliveryData[hashedDeliveryDetails].status = 3;

        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        uint deliveryFee = deliveryData[hashedDeliveryDetails].deliveryFee;

        membersData[deliveryOwner].balance = membersData[deliveryOwner].balance - deliveryFee;

        membersData[assistingMember].reputation = membersData[assistingMember].reputation + 3;
        membersData[assistingMember].balance =  membersData[assistingMember].balance + deliveryFee;

    }
```
Here, you removed the delivery fee from the **delivery owner's** balance and added it to the **assisting member's** balance. 

This is the full code for our logistics smart contract:

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Logistics {
    // The address of the account that launched the smart contract
    address initiator;

    // state data for each member registered on the contract
    struct memberState {
        uint balance;
        uint reputation;
    }

 // state data for each delivery transactions executed through the smart contract
// when status = 0, it means pending
// when status = 1, it means in progress
// when status = 2, it means completed
// when status = 3, it means confirmed
    struct deliveryState {
        uint deliveryFee;
        uint status;
        uint hashedDeliveryDetails;
        address owner;
        address assistingMember;
    }

    // mapping are live arrays with key and value pairs
    mapping (address => memberState) public membersData;
    mapping (address => uint) members;
    mapping (uint => deliveryState) public deliveryData;

    // modifiers
    modifier onlyMember{
        require(members[msg.sender] == 1);
        _;
    }

    // errors
    error notAuthorized(string reason);
    error insufficientFee(string reason, uint registrationFee);

    constructor() public payable {
        initiator = msg.sender;
        // here you will register the initiator of the contract as a member
        members[msg.sender] = 1;
        membersData[msg.sender].balance = msg.value;
        membersData[msg.sender].reputation = 10;
    }

    function register() public payable {
        uint registrationFee = msg.value;
        address newMember = msg.sender;
        if (registrationFee < 10)
           revert insufficientFee({
                reason: "Ether value is less than the required registration fee",
                registrationFee: 10
            });
        members[newMember] = 1;
        membersData[newMember].balance = msg.value;
        membersData[newMember].reputation = 10;
    }

    function requestForDelivery(uint hashedDeliveryDetails) onlyMember public payable {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can request for delivery"
            });

         uint senderReputation = membersData[msg.sender].reputation;
         if(senderReputation < 6)
            revert notAuthorized({
                reason: "You have a low reputation. Complete more deliveries to increase your reputation".
            });

        deliveryData[hashedDeliveryDetails].deliveryFee = msg.value;
        deliveryData[hashedDeliveryDetails].status = 0;
        deliveryData[hashedDeliveryDetails].status = hashedDeliveryDetails;
        deliveryData[hashedDeliveryDetails].owner = msg.sender;

        membersData[msg.sender].balance = membersData[msg.sender].balance + msg.value;
    }

    function respondToDeliveryRequest(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can respond to delivery request"
            });
        uint memberReputation = membersData[msg.sender].reputation;
         if(memberReputation < 1)
            revert notAuthorized({
                reason: "You have exhausted your reputations"
            });
        uint deliveryStatus = deliveryData[hashedDeliveryDetails].status;
        if(deliveryStatus != 0)
            revert notAuthorized({
                reason: "This delivery is no longer available".
            });

        deliveryData[hashedDeliveryDetails].status = 1;
        deliveryData[hashedDeliveryDetails].assistingMember = msg.sender;
    }

    function markDeliveryAsCompleted(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        if(assistingMember != msg.sender) 
            revert notAuthorized({
                reason: "You don't have access to update this delivery".
            });
        deliveryData[hashedDeliveryDetails].status = 2;
    }

    function markDeliveryAsCancled(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        if(assistingMember != msg.sender && msg.sender != initiator) 
            revert notAuthorized({
                reason: "You don't have access to update this delivery"
            });
        
        deliveryData[hashedDeliveryDetails].status = 0;

        if (assistingMember == msg.sender) {
             membersData[msg.sender].reputation = membersData[msg.sender].reputation - 5;
        }

        if (msg.sender == initiator) {
             membersData[msg.sender].reputation = membersData[msg.sender].reputation - 6;
        }
    }

    function markDeliveryAsConfirmed(uint hashedDeliveryDetails) onlyMember public {
        if(members[msg.sender] != 1)
           revert notAuthorized({
                reason: "Only members of this contract can update this delivery"
            });
        address deliveryOwner = deliveryData[hashedDeliveryDetails].owner;
        if(deliveryOwner != msg.sender && msg.sender != initiator) 
            revert notAuthorized({
                reason: "You don't have access to confirm this delivery"
            });
        
        deliveryData[hashedDeliveryDetails].status = 3;

        address assistingMember = deliveryData[hashedDeliveryDetails].assistingMember;
        uint deliveryFee = deliveryData[hashedDeliveryDetails].deliveryFee;

        membersData[deliveryOwner].balance = membersData[deliveryOwner].balance - deliveryFee;

        membersData[assistingMember].reputation = membersData[assistingMember].reputation + 3;
        membersData[assistingMember].balance =  membersData[assistingMember].balance + deliveryFee;

    }

}
```
Congratulations! You just built a Logistics smart contract. 




##Conclusion

In this article, you learned how to build a smart contract to help Logistics companies become more efficient in how they collaborate . In the next article, you would learn how to deploy and test the smart contract.

## Let's connect.
👉 On Twitter [@drayfocus](https://twitter.com/drayfocus)

👉 On LinkedIn  [Akinola Ayomide](https://www.linkedin.com/in/ayomide-akinola-9b940117a/) 

Cheers!