Developing Blockchain App – #1 Create Smart Contract

This series of articles will be 3 parts. We will develop a blockchain app in this series of article. It will be like Hand-on Labs. So we prefer way of learning by developing an application. The subject of this article is to create a smart contract.

Smart Contract ve Solidity

Smart Contract is code parts that also store data in it. Programming language that used to write smart contract is Solidity. It isn't very advanced but it is enough to write.

Let's take an example to better understand smart contract. Shipping date is specified by sellers in the product description. We also order according to this date. But the product may not be shipped in this date. Or it may not be delivered by courier companies. If we look from the viewpoint of buyer, these two problems are not the seller's or the cargo company's. Because our interlocutor is the marketplace. If our order is shipped late or delivered late, we should be able to cancel it. But It is not possible to trust marketplace. Smart contract provide a nice solution. We create this business process on the smart contract and show the contract transparently. So we make sure that the process will be executed. We remember that a published contract can't be change.

Let's start to develop this contract. Our process will include below steps.

  • Own of the contract, so marketplace , will write an order on the contract.
  • Buyer will make payment for the order.
  • If the order isn't sent at specified date, buyer can cancel the order and get her payment back.

Preparing the Environment

Let's install Truffle which provides a development environment. We compile, test and publish of contracts by using Truffle.

npm install -g truffle

Let's install Ganache to create a local blockchain network on our machine. Ganache creates a local local blockchain network accessible on port 7545. This network have 10 accounts with balance of 100ETH. We will make test by using these accounts. You can install from the link below.

Creating Smart Contract

Let's run the following command.

mkdir order-dapp-sample
cd order-dapp-sample
mkdir backend
cd backend
truffle init
code .

Application directory have contracts, migrations ve test directories. We create a contract named Orders.sol under the contracts directory.

We add a migration named 2_deploy_contracts.js under the migrations directory.

We add a test named order.js under the test directory.

We adjust the network section in the file truffle-config.js.

Developing Order Adding and Querying Functions

Marketplace will write OrderId, TotalPrice, DeliveryDate to the contract. So we create a struct named Order. It resembles struct in C#.

We store id as string type because Solidity don't have Guid type. Solidity have numeric types from uint8 to uint256. We use uint256 for totalPrice. Solidity don't have DateTime type. We have to store it in unix timestamp format. So We use uint256 for deliveryDate.

We add the createdDate for creation date of the order, the deliveredDate for delivery date of the order and the status for status of the order. We store status as enum type. It resembles that enum in C# but we can't specify number.

We create a mapping type variable named orders in order to store the order information by id and to query the order by id. It resembles that Dictionary in C#.

Every account or contract on the blockchain has a 160-bit address. We must save the address of the account that created the contract so that only this account can add orders. So only the marketplace can add orders. We access the account address from msg.sender. We get this information from the constructor function and store it in the owner variable. In Solidity, the address data type is used for address information.

We add a function named add to get the order information. We store this information in the orders variable. We need an authorization. So only the contract owner should be able to call this function. For this, we create a modifier named onlyOwner. In this modifier, we compare the account address that calls the function with the account address that creates the contract. If it doesn't match, we throw "The sender isn't authorized" error. Finally, we add this modifier to the add function. We can add many modifiers here. These modifiers are run orderly and the function is run if there is no error.

We add a function named get to return the details about the order. This function takes the order id as parameter. We check the order and if there is no order, we throw an error.

In Solidity, two keywords, memory and storage, are used for reference types. Smart Contracts consume RAM each time a function is called. Variables marked with memory are cleared when the function completes its work. Variables marked with storage live as long as the contract lives.

Since smart contracts cannot be edited, we need to test contracts before publishing. That's why TDD is important. We write tests for the add function in orders.js under the test folder.

When the test starts running, Ganache defines 10 accounts. We can access these accounts from accounts parameter on line 3.

There are three units in the Ethereum network namely Ether, Gwei and Wei. Although Ether is the largest unit, all transactions are made with the smallest unit Wei. In the 8th line, so we convert 2.5 Ether to Wei.

The contract is always created with the first account. In the 16th line, we give the second account to the from parameter so that we expect an error to occur.

We run the Ganache application and click the QuickStart Ethereum button to activate the local blockchain network.

Ganache

We run the test with the following command line.

truffle test ./test/orders.js

The Test Result of The Add Function

Account Status After The Test of Add Function

Developing Order Paying Function

We create a struct named Payment to store the information that we have received the payment. We add id for the id of the order, buyerAddress for the address of the paying account, paidDate for the payment date, and refundedDate for the refund date. We mark the buyerAddress with payable so that we can refund to that address. We create a variable named payments.

Let's publish an event when the payment is made. We create an event named OrderPaid. The event arguments are id, paidAddress, paidAmount and date.

We add a function named pay to receive the payment. This function takes the order id as a parameter. The difference of this function from the add function is that it can receive payment. That's why we add the payable keyword. We check the order. We throw an error if it has already been paid. Then we compare the price of the order with the payment amount and throw an error if they don't match. We access the payment amount from msg.value.

We update the status of the order to Paid. We store the address of the paying account and the payment date. When payment is made with this function, the amount is stored in the contract. Finally, we publish the OrderPaid event.

block.timestamp returns the time of the current block in milliseconds.
msg.value returns the amount entered when the function was executed.
msg.sender returns the address of the account running the function.

We write tests for the pay function in orders.js under the test folder.

Because the pay method is marked as payable, it can receive payment. In the 16th and 27th lines, we enter 200 Wei to the value parameter, we expect an error because the order amount (2.5 Ether) doesn't match 200 Wei.

We published an event when the payment was successful. In the 37th line, we check whether the event is published or not.

We run the test with the following command line.

truffle test ./test/orders.js

The Test Result of The Pay Function

Account Status After The Test of Pay Function

Developing Order Delivering Function

Let's publish an event when the order is delivered. We create an event named OrderDelivered. The event arguments are id and date.

We add a function named deliver to mark the order as delivered. This function takes the order id as a parameter. We also authorize this function for the marketplace. Just like the add function, we add the onlyOwner modifier to this function.

We check the order. We also throw an error if it has already been delivered.

We update the status of the order to Delivered. We add the delivery date of the order. Finally, we publish the OrderDelivered event.

We write tests for the deliver function in orders.js under the test folder.

We run the test with the following command line.

truffle test ./test/orders.js

The Test Result of The Deliver Function

Account Status After The Test of Deliver Function

Developing Order Refunding Function

Let's publish an event when the refund is made. We create an event named OrderRefunded. The event arguments are id and date.

We add a function named refund for process of returning. This function takes the order id as a parameter. We check the order. We throw an error if the order hasn't been paid for. We throw an error if the order has been delivered. We throw an error if a refund has been made. If the account that makes the payment and the account that calls the function are not the same, we throw an error. We throw an error if the promised delivery date hasn't yet been exceeded.

We make the refund to the buyer. We update the status of the order to Refunded. We add the refund ​date. Finally, we publish the OrderRefunded event

In order to test whether the promised delivery date has been exceeded, we need to mock block.timestamp . For this, we add a function named getTime and return the current date from this function.

We add a contract named MockedOrders.sol under the contracts folder. We extend this contract from the Orders contract. We add a function named mockTimestamp. This function takes a timestamp as a parameter and stores it. The getTime function returns this timestamp. We will do our test on this contract.

We edit the 2_deploy_contracts.js migration under the migrations folder.

We write tests for the refund function in orders.js under the test folder.

Solidity has the concepts of Gas, Gas Price and Gas Cost. Each function executed in the contract has a certain cost. Gas is calculated automatically according to the size of the contract and the running code. For example, 1000 Gas is calculated for the pay method. Miners are needed to confirm the transactions made in the contract and create a new block. This is where Gas Price comes into play. Miners work with the Gas * GasPrice calculation. In other words, the higher the Gas Price, the faster the transactions are approved :) The result of this multiplication is called Gas Cost.

In the 69th line, we get Gas Price which default value in Ganache is 20000000000 Gwei.

In the 82th line, we get the balance of the third account. Default 100 Ether in Ganache.

In the 85th to 88th lines, we calculate the Gas Cost for the pay and refund functions. The Gas Price is multiplied by the amount of Gas used.

In the 92th line, we again take the balance of the second account.

In the 93th to 94th lines, we add the Gas Costs that we calculated above to the final balance.

In the 96th line, we compare the balances to check if the refund has been successfully made.

We run the test with the following command line.

truffle test ./test/orders.js

The Test Result of The Refund Function

Account Status After The Test of Refund Function

We have developed the contract. In the next post we will look at how to use it in web application.

You can access the sample application on Github.

Good luck.

15