Implementing a Simple Smart Contract for Asset Tracking
In this post, we'll learn how to create a smart contract with Ethereum and Solidity by following along with this real-world use case.
Join the DZone community and get the full member experience.
Join For FreeIn previous posts, I gave an overview of the challenges of supply chains, an overview of a blockchain-enabled supply chain platform, as well as a hardware implementation for production integration. Now we would like to give some examples for implementing smart contracts that allow asset tracking in the blockchain.
A Simplified Asset Tracking Case
In our simplified case, we would like to track assets throughout the supply chain. In reality, the data structures for the assets might be more complicated, but we will focus on a few aspects only: name, description, and manufacturer. It should be guaranteed that there is only a single instance of an asset at any time which leads to a uniqueness requirement of the ID. We would like to keep track of origin and history of the asset. The key actions that should be supported are the creation of new assets as well as transfers of assets. Furthermore, there should be the possibility to check whether a person is actually in the possession of the asset ownership certificate or not.
The Implementation Environment
As an easy starting point to implement smart contracts, we use the Ethereum blockchain. The key feature of Ethereum is the ability to execute programs on the nodes of the blockchain. It can be seen as an application platform that runs contracts as unstoppable programs on the blockchain. Ethereum has its own virtual machine (Ethereum virtual machine) that executes programs on all nodes the same way. The programming language that defines these programs is called Solidity, which is a
object
contract-oriented, high-level language for implementing smart contracts. Solidity supports inheritance, libraries, complex types, and reads quite similarly to object-oriented programming languages, such as C++, Python, or JavaScript.
Once you have implemented your smart contract functionality, you are ready to either test the contract locally, by deploying it to a test blockchain such as truffle-ganache or to an Ethereum test network such as the Rinkeby test network. Both options will be covered by follow-up articles. The interaction with the contract is done by using either command-line tools such as geth or via web frameworks such as web3.js. To manage your wallets in your browser, you would need a wallet store such as Metamask that connects to either your local or a public test chain as well as the Ethereum network.
Structure of the Smart Contract
Let's have a look a the basic structure of a smart contract written in Solidity.
pragma solidity ^0.4.2;
import "assets.sol";
contract AssetTracker {
string id;
function setId(string serial) public {
id = serial;
}
function getId() public constant returns (string) {
return id;
}
}
The contract file opens with a section on the applicable Solidity language version. Pragmas are known from compilers such as gcc. They give the compiler additional information on how to process the given code. This given pragma tells the compiler to use the ' ^
' highest available language version above '0.4.2.'
Furthermore, you are able to import code from other files at the global scope level by using the well-known 'import' statement, just as in most other languages.
In this example, we have added some public getter and setter functions that can be used both as an internal and as an external function. Beyond this, there is the possibility to create events along the way that can be used to log processes or states. We will make use of the events in order to keep track of supply chain events such as goods receipt or asset creation.
A Brief Excursion to Types and Costs
The static types that are available, such as string, int, bool …, will come with the typical unary and binary operators. You can find a full list of types here. You shouldn't be lazy about learning and using the types properly because this will have an impact on the running cost of your transactions. The transaction effort, described in the unit gas, will depend on the operations executed and the data stored. This effort is priced by you. If you pay more for the execution of your code, your transaction will be preferred by the network, hence executed sooner.
If you want to drill down into the economics of transactions, you should have a look at 'Calculating Cost in Ethereum Contracts' and 'Ethgasstation' to get a feeling about the associated costs with your smart contract. The details of gas cost for opcodes, e.g. the formerly used SSTORE opcode, can be found in the Ethereum yellow paper. Another way to have a look at the cost is to use the online Solidity compiler that allows you to have a look at the Ethereum Virtual machine code generated from your contract. It estimates the gas price and reveals the inner workings of how the different data types are handled on the stack.
Tracking Data Structures
The following struct
describes our simplified asset.
struct Asset {
string name;
string description;
string manufacturer;
bool initialized;
}
We use members, such as describing properties (i.e. name, description and process control variables, such as initialized
and manufacturer
). They are used to check whether this asset was already manufactured and who the manufacturer is.
In order to store the assets, we create two mappings that will allow us to store asset properties as well as the relationship between assets and wallets based on asset uuids.
mapping(string => Asset) private assetStore;
The above is used later on to store assets under their respective uuid:
assetStore[uuid] = Asset(name, description, true, manufacturer);
For the wallet store, we use the following mapping:
mapping(address =>; mapping(string =>; bool)) private walletStore;
This is used later on to make the assignment of an asset to a wallet:
walletStore[msg.sender][uuid] = true;
Declaring the Events
For different real-world events in the supply chain, such as asset creation or asset transfer, we define counterparts in the smart contract.
event AssetCreate(address account, string uuid, string manufacturer);
event RejectCreate(address account, string uuid, string message);
event AssetTransfer(address from, address to, string uuid);
event RejectTransfer(address from, address to, string uuid, string message);
Declaring the Functions
The first function that we would need is the create an asset function. It takes all the information needed to specify the asset and checks if the asset already exists. In this case, we trigger a formerly declared event – RejectCreate()
. If we have a new asset at hand, we store the data in the asset store and create the relation between the message sender's wallet and the asset's uuid.
function createAsset(string name, string description, string uuid, string manufacturer) {
if(assetStore[uuid].initialized) {
RejectCreate(msg.sender, uuid, "Asset with this UUID already exists.");
return;
}
assetStore[uuid] = Asset(name, description, true, manufacturer);
walletStore[msg.sender][uuid] = true;
AssetCreate(msg.sender, uuid, manufacturer);
}
In order to transfer the asset, we create a function that takes the address of the target wallet along with the asset's id. We check two pre-conditions: the asset actually exists and the transaction initiator is actually in possession of the asset.
function transferAsset(address to, string uuid) {
if(!assetStore[uuid].initialized) {
RejectTransfer(msg.sender, to, uuid, "No asset with this UUID exists");
return;
}
if(!walletStore[msg.sender][uuid]) {
RejectTransfer(msg.sender, to, uuid, "Sender does not own this asset.");
return;
}
walletStore[msg.sender][uuid] = false;
walletStore[to][uuid] = true;
AssetTransfer(msg.sender, to, uuid);
}
We would also like to have access to the asset properties by just giving the uuid. Since it is currently not possible to return structs in Solidity, we return a list of strings.
function getAssetByUUID(string uuid) constant returns (string, string, string) {
return (assetStore[uuid].name, assetStore[uuid].description, assetStore[uuid].manufacturer);
}
Furthermore, we would like to have a simple way to prove the ownership of an asset without the need to fiddle around the transaction log. So we create a helper function, isOwnerOf()
.
function isOwnerOf(address owner, string uuid) constant returns (bool) {
if(walletStore[owner][uuid]) {
return true;
}
return false;
}
Once the contract is deployed, we can interface with the smart contract by using web3.js. The following example is an excerpt for creating a new asset.
export const createAssetInContract = async (assetData, publicKey) =>; {
console.log('Creating asset...');
const atContract = await AssetTracker.deployed();
const asset = atContract.createAsset(
assetData.name,
assetData.description,
assetData.assetId,
assetData.manufacturer,
{ from: publicKey },
);
return asset;
};
This simple example shows a basic asset tracking functionality in the blockchain. There are many ways to improve the design and the functionality, such as on-chain uuid generation. If you want to discuss tracking solutions in the blockchain, feel free to start a discussion.
If you are interested in this topic, please let us know. We would like to hear your comments and amendments. Feel free to drop me a few lines and follow me on Twitter: https://twitter.com/kherings
Published at DZone with permission of Kai Herings. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Building a Flask Web Application With Docker: A Step-by-Step Guide
-
Integrating AWS With Salesforce Using Terraform
-
Scaling Site Reliability Engineering (SRE) Teams the Right Way
-
Automating the Migration From JS to TS for the ZK Framework
Comments