This series of articles will consist of 3 parts. In these articles, we will develop a Dapp (decentralized application). It will be like Hands-on Labs. This article is about creating smart contracts.
Smart Contract ve Solidity
Smart Contracts are code parts that run in a blockchain network and store data. So it is a structure that contains both codes and data. Solidity is a programming language that enables us to write contracts.
An example may help you understand smart contracts better. I think we all have had some issues with shipping process after shopping online. We can see the delivery date in product descriptions. So we order according to that information. But we may also encounter two problems here. Sellers may give our orders to shipping companies late. It is also possible that our orders may be delivered late by shipping companies. In these situations, marketplaces are responsible. Because buyers buy their products through marketplaces. When such delays happen, we should be able to cancel the order. But it is not possible to trust marketplaces in this regard. This situation makes us think about how we can use technology to establish that trust. Smart contracts provide a nice solution here. We build such business processes on smart contracts and present those contracts transparently. In this way, we become sure that the process will be executed exactly according to contracts. You should remember that a published contract can’t be changed later.
Then, let’s start to develop such a contract. The process will include the following steps.
- The Contract Owner (marketplace) will write the order in the contract.
- The Buyer will pay for their order.
- If the order doesn’t arrive on the specified date, the buyer will be able to cancel the order and get their money back.
Preparing the Development Environment
Let’s install Truffle. Truffle enables us to compile, test, and publish smart contracts. So it provides a development environment.
npm install -g truffle
We are now installing Ganache to create a local blockchain network in our machine. Ganache creates a local blockchain network that can be accessed on port 7545. This network contains 10 accounts with balance of 100ETH. We will use them for our tests. You can use the link below to make installation according to your operating system.
https://www.trufflesuite.com/ganache
Creating The Smart Contract
Let’s run the following command line.
mkdir order-dapp-sample
cd order-dapp-sample
mkdir backend
cd backend
truffle init
code .
There are three folders in the Application directory. These are contracts, migrations and tests. We create a contract named Orders.sol under the directory “contracts”.
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 edit the network section in the file truffle-config.js.
Developing the Functions “Order Adding” and “Order Fetching”
The marketplace will write the order ID, order price and delivery date on the contract. Therefore, we create a struct named Order. It is similar to the struct in C#.
Since there is no Guid data type in Solidity, we store the ID as string type. In solidity, there are data types that allow us to store numeric data from uint8 to uint256. We use the uint256 data type for totalPrice. Solidity doesn’t have DateTime data type. We have to store it in unix timestamp format. Therefore, we use the uint256 data type for deliveryDate.
We also add createdDate for the order creation date, deliveredDate for the order delivery date, and status for the order status. We store status as enum type. It is similar to enum in C#. But we can’t assign any number.
We create a mapping type variable named orders in order to store the order information and query the order according to the ID. It is like Dictionary in C#.
On blockchain, every account or contract has 160-bit address. We have to save the contract owner address. In this way, we authorize this address for order creation. 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 address type variable named owner. In Solidity, the address data type is used for address information.
We add the function “add” which will provide us order information. We store this information in the orders variable. We need an authorization. Only the contract owner should be able to call this function. For this reason, 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 an error “The sender isn’t authorized”. Finally, we add this modifier to the function “add”. We can add many modifiers here. These modifiers are run orderly, and the function is run if there is no error.
We add the function “get” in order to return the order details. This function takes the order ID as parameter. We check the order and if there is no order, we throw an error.
We need to test contracts before publishing them, as smart contracts cannot be edited again after publishing. That’s why TDD is important. We write tests for the function “add” in orders.js under the test folder.
10 accounts are defined by Ganache when the test starts running. We can access these accounts from the parameter “accounts” on the 3rd line.
There are three units in the Ethereum network namely Ether, Gwei and Wei. Ether is the largest unit but all transactions are made with the smallest unit, Wei. For this reason, we convert 2.5 Ether to Wei in the 8th line.
The contract is always created on the first account. In the 16th line, we give “from parameter” the second account. In this way, we expect an error.
We run the Ganache application and click the QuickStart Ethereum button in order to activate the local blockchain network.
We run the test with the following command line.
truffle test ./test/orders.js
Developing the Function “Order Paying”
We create a Payment struct in order to store the payment information. We add the order id, buyerAddress, paidDate, and refundedDate. We mark the buyerAddress as payable so that we can refund to that address. We add the variable “payments”.
Let’s publish an event after the payment. We create the event “OrderPaid”. The event arguments are id, paidAddress, paidAmount and date.
We add the function “pay” to receive payment. This function takes the order id as a parameter. This function can receive payment, but the function “add” can’t. For that, we add the payable keyword. We check whether the order exists. If not, we throw an error. We throw an error if the payment has been previously made. Then we check whether the payment amount matches the order price and throw an error if they don’t match. We access the payment amount from msg.value.
We update the order status and define it as Paid. We store the paying account address and the payment date. When a payment is made by this function, the amount is stored in the contract. Finally, we publish the event “OrderPaid”.
msg.value returns the amount entered when the function is executed.
msg.sender returns the account address that calls the function.
We write tests for the function “pay” in orders.js under the test folder.
The pay method can receive payment, as it is marked as payable. In the 16th and 27th lines, we give 200 Wei to the parameter “value”, so we expect an error because the order amount (2.5 Ether) doesn’t match 200 Wei.
We have just published an event when the payment was made successfully. In the 37th line, we check whether the event has been published or not.
We run the test with the following command line.
truffle test ./test/orders.js
Developing the Function “Order Delivering”
Let’s publish an event after delivery. We create the event “OrderDelivered”. The event arguments are id and date.
We add the function “deliver” to mark the order as delivered. This function takes the order id as a parameter. We also authorize this function for the marketplace. We add the modifier “onlyOwner” to this function.
We check whether the order exists. If not, we throw an error. We also throw an error if it has been delivered before.
We update the order status. We define it as Delivered. We add the delivery date. Finally, we publish the event “OrderDelivered”.
We write tests for the function “deliver” in orders.js under the test folder.
We run the test with the following command line.
truffle test ./test/orders.js
Developing the Function “Order Refunding”
Let’s publish an event after refunding. We create an event “OrderRefunded”. The event arguments are id and date.
We add the function “refund” for the returning process. This function takes the order id as a parameter. We check whether the order exists. If not, we throw an error. We throw an error if the order payment hasn’t been made. We throw an error if the order has been delivered. We throw an error if a refund has been made. If the paying account 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 been exceeded yet.
We make refund to the buyer. We update the order status and define it as Refunded. We add the refund date. Finally, we publish the event “OrderRefunded”.
In order to test whether the promised delivery date has been exceeded, we need to mock block.timestamp. For this, we add the function “getTime” and return the current date from this function.
We add the contract “MockedOrders.sol” under the folder “contracts”. We inherit this contract from the contract “Orders”. We add the function “mockTimestamp”. This function takes a timestamp as parameter and stores it. The function “getTime” returns this timestamp. We will do our test on this contract.
We edit the migration “2_deploy_contracts.js” under the folder “migrations”.
We write tests for the function “refund” in orders.js under the folder “test”.
Solidity has some terms such as Gas, Gas Price and Gas Cost. Each function executed in the contract has a certain cost. Gas is calculated automatically according to the contract size and the running code. For example, let's say there is an estimated 1000 Gas for the function “pay”. Miners are needed for the confirmation of the transactions made in the contract and for the creation of a new block. This is where Gas Price comes into play. Miners do their job according to 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. Its default value is 20000000000 Gwei in Ganache.
In the 82th line, we get the balance of the third account. The default balance is 100 Ether in Ganache.
In the 85th to 88th lines, we calculate the Gas Cost spent for the functions “pay” and “refund”. The Gas Price is multiplied by the amount of Gas used.
In the 92th line, we again get the balance of the second account.
In the 93th to 94th lines, we add the Gas Costs that we have calculated above to the final balance.
In the 96th line, we compare the balances to check whether the refund has been successfully made.
We run the test with the following command line.
truffle test ./test/orders.js
So we have developed the contract. In the next post, we will look at how to use this contract in a web application.
You can access the sample application on Github.
Good luck.
Comments