Chapter 9

Writing Your Own Smart Contracts with Solidity

IN THIS CHAPTER

Bullet Creating your new smart contract

Bullet Developing functions and events

Bullet Protecting ownership and security

Bullet Making your functions work

You learn about the basics of developing Solidity smart contracts for Ethereum in Chapter 8. You also learn about difficulties encountered with traditional supply chain applications and how Ethereum can help address some of those problems. Developing distributed applications, or dApps, for the Ethereum blockchain may look similar to writing code in other languages, but it does have specific advantages over non-blockchain environments. However, you have to approach the software development process a little differently when working with blockchain.

Before starting to write a dApp for the Ethereum blockchain, make sure that you understand what your dApp should do and why a blockchain environment is a good fit. Getting these points cleared up in the beginning can help you avoid mistakes that waste time and money. Knowing the tips and tricks of blockchain development before you start writing your own dApps will help you develop better software than just learning as you go.

Ethereum dApps focus on providing some functionality that interacts with data stored in the blockchain environment. Due to the design of blockchain technology, each interaction with the blockchain has an associated cost. Understanding how your dApp will have to pay for blockchain access is critical to getting it right the first time. In this chapter, you learn how to use Solidity to write effective smart contracts for the Ethereum blockchain environment.

Reviewing Supply Chain Design Specification

As you discover in Chapter 8, a supply chain is a framework that connects producers to consumers and manages how products and services make their way toward the consumers. In simple cases such as a farmer’s market, consumers buy their produce directly from the growers. But in most other cases, at least one intermediary helps get products from producers to consumers. Intermediaries can provide transportation, warehousing, retailing, and other value-added services.

Implementing a supply chain solution in a blockchain environment can reduce the overall cost of providing products and services to consumers and make the entire process more transparent. If you store every step of a product’s journey on the blockchain, anyone can track the product along its way.

The first step in developing a supply chain dApp is to look at the data and actions the dApp will need to provide the required functionality. For your supply chain dApp to do its job, you need at least four types of data. Here is a list of the types of data you’ll need:

  • Products: This data uniquely identifies a specific product that is eventually bought by a consumer.
  • Participants: This type of data is a description of all supply chain participants, including manufacturers, suppliers, shippers, and consumers.
  • Registrations: This type of data is a snapshot of which participant owns a product at a specific point in time. Registrations track products along the supply chain.
  • Payment token: Participants use payment tokens to pay one another for ownership changes of products. For example, a supplier can purchase a product from a manufacturer and use a payment token to pay the manufacturer.

To provide minimal functionality, your supply chain dApp needs to include the following capabilities:

  • Initialize tokens: Establish an initial pool of payment tokens.
  • Transfer tokens: Move tokens between accounts (that is, pay for products with tokens).
  • Authorize token payments: Allow an account to transfer tokens on behalf of another account.
  • Create products: Create products and show product details.
  • Create participants: Create participants and show participant details.
  • Move products along the supply chain: Transfer product ownership to another participant.
  • Track a product: Show a product’s supply chain history.

The data and functionality your supply chain dApp will support fits nicely into two groups: payment tokens and supply chain. Some of the data and functionality applies to the supply chain, and other data and functionality applies to paying for supply chain activity. You’ll separate your data and functionality into two smart contracts.

Payment token smart contract

The payment token smart contract handles anything related to payments. Your supply chain participants will buy and sell products by using tokens instead of traditional currency. Although Ethereum includes its own currency, Ether, you will implement your own token for supply chain participants to use. Although you could just have participants pay each other by using Ether, a custom token helps you to manage the entire process.

Defining your own token helps ensure that you limit supply chain participation to only valid supply chain participants and can make transfers simpler. Instead of allowing any Ethereum account to interact with your supply chain, only accounts that own your tokens can pay for products. Therefore, the only way a participant can enter the supply chain is to gain the trust of another participant. You have to either sell your products to an existing participant or exchange some other currency for your tokens.

Tip Many businesses use the token concept. Arcades often set up their games to use physical tokens instead of real coins or paper money. You buy tokens using real currency and then use the tokens to play each game. This approach makes breaking into game consoles less attractive because the games contain only tokens — not real money. The tokens have value only inside the arcade. (Also, any lost or misplaced tokens mean a profit for the house; a nice benefit if you’re issuing the tokens.)

Multiple proposed standards for Ethereum tokens in the form of Ethereum Request for Comments (ERC) documents exist. Ten of these proposals have been accepted to become Ethereum Improvement Proposals (EIP). ERC-20 (now EIP-20) defines one of the early standards for defining tokens for Ethereum. You’ll use the ERC/EIP-20 standard for your tokens.

Technical stuff ERC and EIP are used interchangeably. Technically, EIP refers to finalized ERC documents. However, even though a proposal is finalized, such as EIP-20, you still will see it referred to as ERC-20.

Your token smart contract will allow participants to acquire tokens and then transfer them to other participants in exchange for moving products along the supply chain. To complete this process, you need several data items and functions. The data items you’ll define follow:

  • totalSupply: The total number of tokens in circulation
  • name: A descriptive name for your token
  • decimals: The number of decimals to use when displaying token amounts
  • symbol: A short identifier for your token
  • balances: The current balance of each participating account, mapped to the account's address
  • allowed: A list of number of tokens authorized for transfer between accounts, mapped to the sender’s address

Your token smart contract will define six functions that allow users to manage token transfers. The functions you’ll define are as follows:

  • totalSupply(): Returns the current total number of tokens
  • balanceOf(): Returns the current balance, in tokens, of a specific account
  • allowance(): Returns the remaining number of tokens that are allowed to be transferred from a specific source account to a specific target account
  • transfer(): Transfers tokens from the caller to a specified target account
  • approve(): Sets a number of tokens that are allowed to be transferred from a specific source account to a specific target account
  • transferFrom(): Transfers tokens from a specified source account to a specified target account

Supply chain smart contract

Your second smart contract will contain the data and functionality to manage the product, participant, and product transfer data. In other words, it will handle all supply chain activity that isn't related to payment. As you learn how to implement supply chain functionality, you’ll probably think of more things that you’d like your dApp to handle. That’s okay. The smart contracts you’ll develop in this book are just a starting point. You can extend them to handle many more use cases.

To store the supply chain data necessary for managing product migration toward consumers, you’ll define the following data items:

  • product structure: This data item stores data that defines a unique product (model number, part number, serial number, product owner, cost, manufactured time).
  • participant structure: The participant structure stores data that defines a unique participant (user name, password, participant type, Ethereum address).
  • registration structure: The registration structure stores data that records a transfer of a product from one owner to another as the product moves toward the consumer (product ID, owner ID, transaction time, product owner Ethereum address).
  • p_id: The product ID uniquely identifies a product and is mapped to a product structure.
  • u_id: The participant ID uniquely identifies a participant and is mapped to a participant structure.
  • r_id: The registration ID uniquely identifies a registration and is mapped to a registration structure.

Now that you know the data and functions you'll need in your smart contracts, the next step is to start writing code in Solidity.

Creating New Smart Contracts

In this section you create the files you need to implement the token and supply chain smart contracts. To get started, follow these steps to create a new project named SupplyChain, initialize it in Truffle, and launch VS Code for your new project:

Remember You learn how to create projects, initialize them in Truffle, and use VS Code to edit files and code for projects in Chapters 5 and 7. Review those chapters if you need details for each step. (If you’d rather download the project files instead of creating a new empty project, go to www.dummies.com/go/ethereumfd and extract the project archive file to a directory of your choice.)

  1. Open a command shell or PowerShell window.
  2. Type the following to create a new project folder:

    mkdir SupplyChain

  3. Change the current folder to the new project folder:

    cd SupplyChain

  4. Initialize the new project in Truffle:

    truffle init

  5. Launch VS Code for the new SupplyChain project:

    code .

In VS Code, create three new smart contracts as follows. Click or tap SUPPLYCHAIN, then Contracts, and then the New File button next to SUPPLYCHAIN. Type the following filenames to create each new file (make sure your filenames appear under Contracts and look exactly like these). (If you download the project files, you don’t have to do this.)

  • erc20Interface.sol
  • erc20Token.sol
  • SupplyChain.sol

Now click or tap SUPPLYCHAIN and then Contracts to display your contracts. Your VS Code Explorer view should look like Figure 9-1.

Screen capture depicting VS Code Explorer view.

FIGURE 9-1: Supply chain starting smart contracts in VS Code.

Tip Don’t worry if you mistype a filename. Changing filenames in VS Code is easy. Just click or tap the filename in VS Code Explorer, and press F2. Now you can type the new filename. If you created your files in the wrong folder, you can fix that in VS Code as well. Just drag them to the right place (under Contracts).

ERC-20 token interface

The first file you’ll edit is the interface for the ERC-20 token. An interface looks just like a smart contract but doesn’t contain any executable code. Developers use interfaces to define minimum functionality for groups of programs. When you define an interface, you define the minimum data items and functions that you want to be common among smart contracts that implement the interface.

Technical stuff ERC-20 tokens aren’t the only type of tokens in Ethereum. Another token standard that looks like it may challenge ERC-20’s popularity is ERC-223. You can find out more about the ERC-223 token standard at https://medium.com/kinblog/the-new-erc223-token-standard-8dddbf1a5909.

In our case, we’re going to use the standard ERC-20 (or EIP-20) token interface. Every ERC-20 token that uses this interface is guaranteed to have the same minimum data and functions. That’s the purpose of an interface. Your implementation may have more data and functions, but you can count on the fact that it has at least everything defined in the interface. In fact, if you use an interface and forget to define a data item or function, the compiler generates an error and refuses to compile the program.

To make sure that your token complies with the ERC-20 token standard, you use the ERC-20 token interface. Click or tap the erc20Interface.sol tab in VS Code and enter the following code:

// ----------------------------------------------------------------------------

// ERC Token Standard #20 Interface

// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

// ----------------------------------------------------------------------------

pragma solidity ^0.4.24;

contract ERC20Interface {

uint256 public totalSupply;

function totalSupply() public view returns (uint);

function balanceOf(address tokenOwner) public view returns (uint balance);

function allowance(address tokenOwner, address spender) public view returns (uint remaining);

function transfer(address to, uint tokens) public returns (bool success);

function approve(address spender, uint tokens) public returns (bool success);

function transferFrom(address from, address to, uint tokens) public returns (bool success);

event Transfer(address indexed from, address indexed to, uint tokens);

event Approval(address indexed tokenOwner, address indexed spender, uint tokens);

}

This interface defines the single variable, six functions, and two events that every token contract must implement to support the ERC-20 token standard. You’ll learn what each line of code does in the next section, where you implement the interface. At this point, just note that each line of code defines a variable, a function, or an event. There is no code that actually does anything.

ERC-20 token smart contract

After you define the interface, you can implement the code to make your token smart contract work. Click or tap the erc20Token.sol tab in VS Code and enter the following code:

// ----------------------------------------------------------------------------

///Implements EIP20 token standard: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

// ----------------------------------------------------------------------------

pragma solidity ^0.4.24;

import "./erc20Interface.sol";

contract ERC20Token is ERC20Interface {

}

You learned about the pragma statement in Chapter 7. The import statement tells Solidity to open an external file and read it into this file to compile it, just as if you had copied the contents of erc20Interface.sol into this file. You can use the import statement as your projects grow to help you keep any single source code file from growing too large to manage easily. If any source code file starts to get too big, you can spilt it into multiple smaller files and import the pieces in the main file.

The last line in the code segment defines the smart contract. You use the contract statement to define a smart contract named ERC20Token. When you define the contract as ERC20token is ERC20Interface, you are telling Solidity that you intend to implement ERC20Interface in this file. Therefore, the compiler will check to make sure that every item defined in the interface is implemented in this file.

The first step to make your token work is to add the data items you'll need. Add the following code between the two curly braces (after the contract statement):

uint256 constant private MAX_UINT256 = 2**256 - 1;

mapping (address => uint256) public balances;

mapping (address => mapping (address => uint256)) public allowed;

uint256 public totalSupply; // Total number of tokens

string public name; // Descriptive name (i.e. For Dummies Token)

uint8 public decimals; // How many decimals to use to display amounts

string public symbol; // Short identifier for token (i.e. FDT)

These lines of source code define the data you’ll store on the blockchain for your token. The first data item, MAX_UINT256, is defined as a constant, which means that the value you assign to it can't be changed at runtime. Solidity uses the ** symbol to denote exponentiation, so 3 squared is 3**2 in Solidity. The value of 2 raised to the 256 power minus 1 is stored in the MAX_UINT256 constant. Defining MAX_UINT256 is a convenient way to store the largest possible value in a uint256 variable.

The balances and allowed data items are mappings. They exist to make it easy to look up a balance or a list of token transfer allowances for an Ethereum account address. The remaining data items are state variables that describe attributes of your token.

Technical stuff State variables are stored in the blockchain, and storing data in the blockchain costs gas. So you can conserve gas by minimizing how much and how often you store blockchain data. Don't define more state variables than you need. It is good practice to declare the smallest uint size for the data you’ll store, which is why the decimal variable is defined as uint8. When you define more complex data using structs, size matters even more.

Supply chain smart contract

The next step in writing developing your supply chain dApp is to begin the definition of the supply chain smart contract. Click or tap the SupplyChain.sol tab in VS Code and enter the following code:

pragma solidity ^0.4.24;

contract supplyChain {

uint32 public p_id = 0; // Product ID

uint32 public u_id = 0; // Participant ID

uint32 public r_id = 0; // Registration ID

}

This smart contract starts like the other smart contracts you’ve seen so far. Inside the contract body, you define three state variables to store the highest ID for products, participants, and registrations. The next sections define the details of each type of supply chain data.

Product structure

The product structure defines the details for each unique product. Type the following code after the state variable definitions in the preceding section:

struct product {

string modelNumber;

string partNumber;

string serialNumber;

address productOwner;

uint32 cost;

uint32 mfgTimeStamp;

}

mapping(uint32 => product) public products;

In addition to the product structure, the products mapping allows users to look up a product from its product ID (p_id).

Participant structure

The participant structure defines the details for each unique participant. Type the following code after the products mapping in the preceding section:

struct participant {

string userName;

string password;

string participantType;

address participantAddress;

}

mapping(uint32 => participant) public participants;

In addition to the participant structure, the participants mapping allows users to lookup a participant from its participant ID (u_id).

Registration structure

The registration structure defines the details for each unique registration. A registration is defined as the point in time when a product's owner changes from one participant to another. Each registration represents a product moving along the supply chain.

Type the following code after the participants mapping in the preceding section:

struct registration {

uint32 productId;

uint32 ownerId;

uint32 trxTimeStamp;

address productOwner;

}

mapping(uint32 => registration) public registrations; // Registrations by Registration ID (r_id)

mapping(uint32 => uint32[]) public productTrack; // Registrations by Product ID (p_id) / Movement track for a product

In addition to the product structure, the registrations mapping allows users to look up a registration from its registration ID (r_id). The productTrack mapping returns the supply chain movement history for a specified product (p_id).

Coding Primary Functions

After defining the basic contract structure and data items, the next step in developing your smart contracts is to write the code for each smart contract function. Functions provide the actions of your smart contracts and define what your smart contracts can do.

ERC-20 token functions

To define the actions your ERC-20 token smart contract should carry out, you need to define its functions and provide the code for the body of each function. Remember that your ERC-20 token smart contract implements an interface, so you have to at least define the functions required in the interface. Note that you can define more functions than those in the interface.

Click or tap the erc20Token.sol tab in VS Code and enter the code for each of the following functions. (Start entering function source code after the symbol state variable definition.)

ERC-20 token constructor

A constructor is a special type of function that runs when the smart contract is deployed to the blockchain. In the constructor, you place initialization steps that are executed only when the contract is first stored in the blockchain. In the case of the ERC-20 token, the constructor initializes the token's attributes and allocates the supply of tokens to the Ethereum address that deploys the smart contract.

Enter the following code to define the smart contract’s constructor:

constructor(uint256 _initialAmount, string _tokenName,

uint8 _decimalUnits, string _tokenSymbol) public {

balances[msg.sender] = _initialAmount; // The creator owns all tokens

totalSupply = _initialAmount; // Update total token supply

name = _tokenName; // Token name

decimals = _decimalUnits; // Number of decimals

symbol = _tokenSymbol; // Token symbol

}

Tip You can use any naming convention for function parameters and variables. Many Solidity developers use the underscore character as the first character for function parameter names. That convention makes it easy to tell whether a data item is a variable or a parameter passed into a function. In this book, parameter names start with the underscore character.

The constructor code for the ERC-20 token is simple. When you deploy the smart contract code to the blockchain, the smart contract assigns the token data items to the provided parameters and assigns all initial tokens to the Ethereum account address that deployed the smart contract.

Defining the transfer() function

The transfer() function transfers tokens from the calling address to a specified address. Enter the following code after the constructor:

function transfer(address _to, uint256 _value) public returns (bool success) {

require(_value >= 0,"Cannot transfer negative amount.");

require(balances[msg.sender] >= _value,"Insufficient funds.");

balances[msg.sender] -= _value;

balances[_to] += _value;

return true;

}

The transfer() function introduces the Solidity require() function, which prevents functions from continuing unless a specific condition is met. If the require() condition is not satisfied, it returns a message to the caller and refunds any unused gas. It provides a polite way to stop smart contract execution. In the case of the transfer() function, you can transfer tokens only if the sender has a sufficient balance to transfer. The require() condition validates that the sender has at least as many tokens as the transfer requires; otherwise, it returns the "Insufficient funds." string.

If the sender does have the required funds, you decrease the balance of the sender and increase the balance of the receiver by the amount to transfer, and then return a true value that tells the caller that the transfer was completed successfully.

Defining the transferFrom() function

The transferFrom() function transfers tokens from one specified address to another specified address. Enter the following code after the transfer() function:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {

uint256 allowance = allowed[_from][msg.sender];

require(balances[_from] >= _value && allowance >= _value,"Insufficient funds.");

balances[_from] -= _value;

balances[_to] += _value;

if (allowance < MAX_UINT256) {

allowed[_from][msg.sender] -= _value;

}

return true;

}

The transferFrom() function transfers up to a pre-approved amount from one address to another. The function looks up the pre-approved amount from the allowed mapping and stores that value in the allowance variable. The function calls the require() function to verify that the sender has a sufficient token balance to transfer, and then adjusts the balances of the sender and receiver. The last step is to query the allowance variable and, if it is set, subtract the amount transferred from the remaining allowance.

Defining the balanceOf() function

The balanceOf() function returns the number of tokens owned by a specified address. Enter the following code after the transferFrom() function:

function balanceOf(address _owner) public view returns (uint256 balance) {

return balances[_owner];

}

Defining the approve() function

The approve() function grants permission to transfer a specified number of tokens from one address to another specified address. Enter the following code after the balanceOf() function:

function approve(address _spender, uint256 _value) public returns (bool success) {

allowed[msg.sender][_spender] = _value;

return true;

}

Defining the allowance() function

Th allowance() function returns the remaining number of approved tokens that can be transferred from one address to another specified address. Enter the following code after the approve() function:

function allowance(address _owner, address _spender) public view returns (uint256 remaining) {

return allowed[_owner][_spender];

}

Defining the totalsupply() function

The totalSupply() function returns the total number of tokens in circulation. Enter the following code after the allowance() function:

function totalSupply() public view returns (uint256 totSupp) {

return totalSupply;

}

Supply chain functions

To define the functionality of your supply chain smart contract, you need to define its functions and provide the code for the body of each function.

Click or tap the SupplyChain.sol tab in VS Code and enter the code for each of the following functions. (Start entering function source code after the productTrack mapping definition.)

Defining the participant functions

The createParticipant() function increments the participant ID (u_id), creates a new participant, and sets its attributes to the passed-in parameters. Enter the following code after the productTrack mapping:

function createParticipant(string _name, string _pass, address _pAdd, string _pType) public returns (uint32){

uint32 userId = u_id++;

participants[userId].userName = _name ;

participants[userId].password = _pass;

participants[userId].participantAddress = _pAdd;

participants[userId].participantType = _pType;

return userId;

}

The getParticipantDetails() function returns the attributes of the specified participant (p_id). Enter the following code after the createParticipant() function:

function getParticipantDetails(uint32 _p_id) public view returns (string,address,string) {

return (participants[_p_id].userName,participants[_p_id].participantAddress,participants[_p_id].participantType);

}

Defining the product functions

The createProduct() function increments the product ID (p_id), creates a new product, and sets its attributes to the passed-in values. Enter the following code after the getParticipantDetails() function:

function createProduct(uint32 _ownerId, string _modelNumber, string _partNumber, string _serialNumber, uint32 _productCost) public returns (uint32) {

if(keccak256(abi.encodePacked(participants[_ownerId].participantType)) == keccak256("Manufacturer")) {

uint32 productId = p_id++;

products[productId].modelNumber = _modelNumber;

products[productId].partNumber = _partNumber;

products[productId].serialNumber = _serialNumber;

products[productId].cost = _productCost;

products[productId].productOwner = participants[_ownerId].participantAddress;

products[productId].mfgTimeStamp = uint32(now);

return productId;

}

return 0;

}

Tip Unlike many other languages, you can't directly compare strings in Solidity. You have to first calculate a hash value of the string and then compare that number to the hash value of another string. If the two hash values are equal, the strings are equal. Solidity includes the keccak256() function to calculate hashes. To calculate a hash value that you can use in a comparison, you have to call the convert to string function by using the api.encodePacked() function, and then call the keccak256() function on the encoded string.

The getProductDetails() function returns the attributes of the specified product (p_id). Enter the following code after the createProduct() function:

function getProductDetails(uint32 _productId) public view returns (string,string,string,uint32,address,uint32){

return (products[_productId].modelNumber,products[_productId].partNumber,

products[_productId].serialNumber,products[_productId].cost,

products[_productId].productOwner,products[_productId].mfgTimeStamp);

}

Defining the supply chain movement functions

The transferToOwner() function records movement along the supply chain. This function transfers the ownership of a specified product from one supply chain participant to another. It creates a new registrations struct, based on r_id, assigns its data items from the passed-in parameters, and pushes the new struct onto the productTrack list. Enter the following code after the getProductDetails() function:

function transferToOwner(uint32 _user1Id ,uint32 _user2Id, uint32 _prodId) public returns(bool) {

participant memory p1 = participants[_user1Id];

participant memory p2 = participants[_user2Id];

uint32 registration_id = r_id++;

registrations[registration_id].productId = _prodId;

registrations[registration_id].productOwner = p2.participantAddress;

registrations[registration_id].ownerId = _user2Id;

registrations[registration_id].trxTimeStamp = uint32(now);

products[_prodId].productOwner = p2.participantAddress;

productTrack[_prodId].push(registration_id);

return (true);

}

Tip You define two local variables, p1 and p2, to temporarily hold the source and target participant data. However, Solidity would create these by default as storage variables because they directly reference storage structs. You have to tell Solidity to make these variable local by using the memory modifier. That reduces the gas cost of your smart contract.

The getProductTrack() function returns the registration history for a specified product. This function shows the path a product has taken along the supply chain from its original producer. This function provides the current status of any product in the supply chain. Enter the following code after the transferToOwner() function:

function getProductTrack(uint32 _prodId) external view returns (uint32[]) {

return productTrack[_prodId];

}

The getRegistrationDetails() function returns the attributes of the specified registration (r_id). Enter the following code after the getProductTrack() function:

function getRegistrationDetails(uint32 _regId) public view returns (uint32,uint32,address,uint32) {

registration memory r = registrations[_regId];

return (r.productId,r.ownerId,r.productOwner,r.trxTimeStamp);

}

Using Events

Smart contract code runs on each EVM across the Ethereum network. It is essentially server-side code. One of the difficulties you encounter when running server-side code is communicating with the client. Smart contracts don't just run arbitrarily — they have to be called by a client or another smart contract. Communication is pretty easy when a client or smart contract calls another smart contract function. The caller sends input parameters and waits to receive any return values.

Technical stuff One of the more interesting features of Solidity is that its functions can return multiple values. Many languages allow functions to return only a single value, so developers have to figure out ways to pack multiple data items into return strings and unpack them on the client side. Take a look at your getProductDetails() and getParticipantDetails() functions in the SupplyChain.sol file to see how Solidity passes back multiple return values.

Sometimes the caller doesn't want to wait around for a function to finish. Many programs operate under a different flow control model. In an event-driven model, one program waits for something to happen and then responds. VS Code operates in an event-driven model. Figure 9-2 shows VS Code as you edit the SupplyChain.sol smart contract.

Screen capture depicting VS Code as you edit the SupplyChain.

FIGURE 9-2: Editing SupplyChain.sol in VS Code.

Your VS Code window should look like the one in the figure. The program is running, but it isn’t doing much right now. In fact, VS Code is waiting on you to do something. After you launch VS Code and open SupplyChain.sol, VS Code just waits for you to tell it what to do. You can type text into the editor, click or tap menu items or buttons, or press function keys of control key combinations.

You can code event-driven programs in Solidity, too. Solidity allows you to define events in your smart contracts and then trigger those events whenever you want to. If your calling program is waiting, or listening, for these events, it can respond to the events and carry out some of its own functions. In the case of VS Code, it is listening for many events, including the Ctrl+S keystroke event. When VS Code sees that the Ctrl-S event has occurred, it runs its Save File function. You can code your smart contracts the same way.

Technical stuff Event-driven programming is often described as a publish-subscribe approach to program flow. The called programs generate, or publish, events when interesting things happen. The called program doesn’t care what programs are listening for the events, if any. Publishing events allows the called programs to communicate asynchronously with any listening programs. Programs that want to listen for, and respond to, events must subscribe to those events. It works just like subscribing to a local newspaper. The newspaper publishes a paper every day, but only its subscribers receive the paper.

There are three steps to implanting events in Solidity:

  1. Define the event.

    Use the Solidity event statement to define the event, give it a name, and define the data it passes when it triggers.

  2. Trigger the event.

    Use the Solidity emit statement to trigger a previously defined event and pass data to it.

  3. Receive and respond to the event.

    You learn how to receive and respond to events in Chapter 10.

Defining events

Your supply chain smart contracts will use three events. The ERC-20 token interface requires that you implement two of the events, and you’ll define the other one in the main supply chain smart contract.

Click or tap the erc20Interface.sol tab in VS Code to switch to the ERC-20 interface code. Look at the last two lines of code:

event Transfer(address indexed from, address indexed to, uint tokens);

event Approval(address indexed tokenOwner, address indexed spender, uint tokens);

These lines define the Transfer and Approval events. When the Transfer event triggers, it tells any program that is listening for this event that a transfer has just occurred, and that the transfer consisted of send tokens from one address to another. When the Approval event triggers, it tells any program that is listening for this event that a new approval was just authorized for a token owner to transfer up to some number of tokens to a specific sender. It is up to the listening programs to do something with these events. Your smart contracts need to only define and trigger the events.

Click or tap the SupplyChain.sol tab in VS Code to switch to the supply chain smart contract code. Scroll down to the definitions for the two mappings after the registrations struct. This should be around line 35. Type the following text on a new line:

event Transfer(uint32 productId);

After you enter this new code, the last few lines of your SupplyChain.sol code should look like Figure 9-3.

Screen capture depicting the last few lines of your SupplyChain.sol code.

FIGURE 9-3: Defining an event in VS Code.

You will use your new Transfer event every time you create a new registration. That means you'll use the event to let any listening program know that a product has just been transferred from one supply chain participant to another. The Transfer event sends the transferred product ID to the listening program to tell it what product just moved. The event mechanism makes it easy for external programs to monitor changes that your smart contracts carry out.

Triggering events

After you define the events you’ll need in your smart contracts, the next step is to trigger each event at the right time. All you really have to do here is trigger the event in your code when you carry out the action you want to communicate. In other words, you should trigger the Transfer event at the point where your code carries out the transfer action.

In Solidity, you use the emit statement to trigger an event. When you use the emit statement, it is like calling a function. You tell Solidity what event to trigger, and then you provide parameter values, as you do with a function.

Click or tap the erc20Interface.sol tab in VS Code to switch to the ERC-20 smart contract code. Update your transfer(), transferFrom(), and approve() functions to include the emit statements shown here (new code is in bold):

function transfer(address _to, uint256 _value) public returns (bool success) {

require(balances[msg.sender] >= _value,"Insufficient funds for transfer source.");

balances[msg.sender] -= _value;

balances[_to] += _value;

emit Transfer(msg.sender, _to, _value);

return true;

}

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {

uint256 allowance = allowed[_from][msg.sender];

require(balances[_from] >= _value && allowance >= _value,"Insufficient allowed funds for transfer source.");

balances[_to] += _value;

balances[_from] -= _value;

if (allowance < MAX_UINT256) {

allowed[_from][msg.sender] -= _value;

}

emit Transfer(_from, _to, _value);

return true;

}

function approve(address _spender, uint256 _value) public returns (bool success) {

allowed[msg.sender][_spender] = _value;

emit Approval(msg.sender, _spender, _value);

return true;

}

You can call the emit statement anywhere in your smart contract code. In your ERC-20 token smart contract, you have added the emit statement in three places to signal that some notable action has just occurred.

Click or tap the SupplyChain.sol tab in VS Code to switch to the supply chain smart contract code. Update your transferToOwner() function to include the emit statement shown here (new code is in bold):

function transferToOwner(uint32 _user1Id ,uint32 _user2Id, uint32 _prodId) onlyOwner(_prodId) public returns(bool) {

participant memory p1 = participants[_user1Id];

participant memory p2 = participants[_user2Id];

uint32 registration_id = r_id++;

registrations[registration_id].productId = _prodId;

registrations[registration_id].productOwner = p2.participantAddress;

registrations[registration_id].ownerId = _user2Id;

registrations[registration_id].trxTimeStamp = uint32(now);

products[_prodId].productOwner = p2.participantAddress;

productTrack[_prodId].push(registration_id);

emit Transfer(_prodId);

return (true);

}

Introducing Ownership

One of the difficulties you'll encounter when developing blockchain applications is restricting the execution of sensitive functions. Remember that your smart contract code runs on all EVMs. All EVMs have the complete code for your smart contracts, so limiting execution requires careful planning.

Modifiers can make data items and functions unavailable for external entities to access or run. Don’t make the mistake of thinking modifiers make smart contracts secure. They can help reduce the availability to external programs, but they don’t provide complete security. Remember that data on a public blockchain is there for anyone to see.

Every smart contract invocation has a caller address. Each EVM knows which account carries out each action. In Solidity, you can access the calling account by referencing msg.sender. Open the erc20Token.sol smart contract and look at the first line of the constructor body:

constructor(uint256 _initialAmount, string _tokenName,

uint8 _decimalUnits, string _tokenSymbol) public {

balances[msg.sender] = _initialAmount; // The creator owns all tokens

totalSupply = _initialAmount; // Update total token supply

name = _tokenName; // Token name

decimals = _decimalUnits; // Number of decimals

symbol = _tokenSymbol; // Token symbol

}

The first thing you do when the constructor executes (that is, when you deploy this smart contract), is to assign the total initial number of tokens to the calling address’s balance value. Solidity stores the value of the caller’s address in the msg.sender value, so you can use that to refer to the sender in your code.

You can use the msg.sender value also to define ownership and enforce access restrictions in your smart contract code. In your supply chain smart contract, you don't want anyone to be able to transfer a product to another participant. Doing so would allow one participant to steal products from others. The transfer process in your supply chain smart contract transfers the ownership of a product as the product moves along the supply chain. It makes sense that only the current owner should be allowed to transfer a product to another owner.

Tip Ownership is a common concern when buying and selling products. If you sell a car, you have to sign the title over to the new owner. Possession of the car’s title proves ownership. As the car’s current owner, you must ensure that no one else can transfer the title of your car to another owner.

Solidity provides modifiers to help make the task of enforcing ownership easier. A modifier is like a lightweight function. It has a name, input parameters, and a body, but it doesn’t support return values.

Open the SupplyChain.sol smart contract in VS Code and add the following code after the createProduct() function:

modifier onlyOwner(uint32 _productId) {

require(msg.sender == products[_productId].productOwner );

_;

}

This code defines the onlyOwner modifier. You can use this modifier on any function to allow only a product's current owner to execute that function. The modifier takes a product ID as an input parameter and checks to see if the msg.sender is the same address as the product’s current owner address. If the two values match, the modifier is satisfied and the function proceeds. If the two address values do not match, msg.sender is not allowed to run the function and control returns to the caller.

Tip When you write modifiers, don't forget to add the _; line as the last line in the modifier body. This tells Solidity to proceed to the function that you have modified. You’ll only get to that line if the modifier body is satisfied (that is, the condition in the body is true.)

You invoke modifiers by adding them to existing functions. Scroll to the transferToOwner() function in the supply chain smart contract code and add the onlyOwner(_prodId) modifier to the function header. Your modified function header should look like this:

function transferToOwner(uint32 _user1Id ,uint32 _user2Id, uint32 _prodId) onlyOwner(_prodId) public returns(bool) {

This modifier tells Solidity that before you execute the transferToOwner() function, execute the onlyOwner() modifier. The modifier determines whether the function caller (msg.sender) is the owner of product prodId. If msg.sender is the product owner, the transferToOwner() function proceeds. If not, it just returns without transferring the product.

You can use modifiers to carry out any validation steps that should occur before you run code in a function. Validating ownership is one of the more common uses of modifiers.

Designing for Security

Solidity smart contract code can be as insecure as any other software. Just because your code runs in a blockchain environment doesn't mean that it’s secure. You have to keep security in mind throughout the entire software development process. Although you need to consider many things when developing secure software in any environment, developing blockchain dApps requires that you pay special attention to the distributed nature of the blockchain.

Tip For a great online resource for Ethereum Smart Contract Security Best Practices, go to https://consensys.github.io/smart-contract-best-practices. Read through the explanations and recommendations in this resource for a more complete understanding of security issues in Ethereum application development.

When you’re developing Ethereum dApps, you’ll have to avoid many security weaknesses. Table 9-1 lists a few of the most common security mistakes new Ethereum developers tend to overlook.

TABLE 9-1 Common Ethereum Security Mistakes

Security Mistake

Description

How to Avoid

Lack of randomness

Because smart contract code runs on every EVM, generating random numbers can cause code to run differently on different EVMs.

Use only random numbers that do not affect stored data or smart contract execution flow.

Allowing re-entrancy

The call function forwards all the received gas to the called function. If your code allows a function to run multiple times before changing state data, that code could allow multiple changes, such as multiple withdrawals.

Always update state data before transferring control to another function.

Not checking for overflow and underflow

Incrementing an integer larger than its maximum value or decrementing an integer smaller than the minimum value causes an error that reverts a smart contract.

Always check to make sure that increment and decrement operations do not overflow or underflow.

Permitting delegatecall with visible functions

Delegatecall allows a smart contract to execute a function from another contract, running it using the calling contract's address. Public or external functions that modify state may be able to do so without being detected.

Limit the use of public and external functions that modify state data.

Implementing Minimal Functionality

If you’ve followed the discussion throughout this chapter, your ERC-20 and supply chain smart contracts should be complete and clean. That is, they should compile with no errors. In VS Code, press the F5 key to start the Solidity compiler. Figure 9-4 shows the results of the Solidity compiler.

Screen capture depicting results of the Solidity compiler.

FIGURE 9-4: Compiler output.

Note that the Output tab shows a warning. In the SupplyChain.sol smart contract’s transferToOwner() function, you defined the p1 variable but never used it in the function.

Remember The compiler warns you that you left an unused variable in your code. Although this is a harmless warning, don’t make a habit of ignoring compiler warnings. They exist for good reasons, and you should fix each one.

Click or tap the Problems tab to see a list of all problems the compile encountered, as shown in Figure 9-5. Click or tap any problem. VS Code highlights the problem and highlights the line in your source code that corresponds to the problem. If you hover your cursor over the problem or the line of code, more help appears in a pop-up. VS Code provides multiple ways to help you identify and fix problems with your code.

Screen capture depicting a list of all problems the compile encountered.

FIGURE 9-5: Compiler problems.

If your smart contracts don’t compile cleanly, review the code to make sure that what you entered is exactly what you see in the book. Pay attention to capitalization — it matters. Another common problem is copying code and pasting it into an editor. Make sure that you copied just what you expected, and look closely at copied quotes. In many cases, copied and pasted quotes result in a quote that won’t compile. If you suspect that problem, just backspace over any pasted quotes and type them directly.

Because it is so easy to introduce minor errors that can stop code from compiling, keep your code as simple as possible As you develop your own smart contracts, implement only the minimal functionality first. You can always add more features after you get the simple code working.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset