Difference between revisions of "Ethereum Smart Contracts Tutorial"

From Grundy
Jump to navigation Jump to search
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
What lies at the core of Decentralised Applications built on blockchains? How do I implement my business logic into a blockchain? What programming languages do I need to be familiar with? All these, and many more such questions would be answered through this article on writing and testing Smart Contracts on Ethereum. This tutorial focuses on the '''Solidity''' (the language for writing smart contracts), '''Javascript''' (for testing them) and some basic frameworks like '''Truffle''' (to deploy and interact with smart contracts) and '''Ganache''' (to spin up a local blockchain for testing).
 
What lies at the core of Decentralised Applications built on blockchains? How do I implement my business logic into a blockchain? What programming languages do I need to be familiar with? All these, and many more such questions would be answered through this article on writing and testing Smart Contracts on Ethereum. This tutorial focuses on the '''Solidity''' (the language for writing smart contracts), '''Javascript''' (for testing them) and some basic frameworks like '''Truffle''' (to deploy and interact with smart contracts) and '''Ganache''' (to spin up a local blockchain for testing).
  
== What are Smart Contracts?==
+
= What are Smart Contracts?=
 
As per the definition by Investopedia, "''Smart contracts are self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. The code and the agreements contained therein exist across a distributed, decentralized blockchain network. Smart contracts permit trusted transactions and agreements to be carried out among disparate, anonymous parties without the need for a central authority, legal system, or external enforcement mechanism. They render transactions traceable, transparent, and irreversible.''"
 
As per the definition by Investopedia, "''Smart contracts are self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. The code and the agreements contained therein exist across a distributed, decentralized blockchain network. Smart contracts permit trusted transactions and agreements to be carried out among disparate, anonymous parties without the need for a central authority, legal system, or external enforcement mechanism. They render transactions traceable, transparent, and irreversible.''"
  
 
Smart Contracts form the crux of Decentralised Applications (DApps), aiding in implementing the business logic through codified contracts. When parties engaged in the contract fulfil certain obligations, the contract ''smartly'' knows what action to take (such as releasing funds on receipt of a deliverable). And since these contracts are deployed on a blockchain, there is no 3rd-party involved and transactions remain transparent and verifiable by anyone.
 
Smart Contracts form the crux of Decentralised Applications (DApps), aiding in implementing the business logic through codified contracts. When parties engaged in the contract fulfil certain obligations, the contract ''smartly'' knows what action to take (such as releasing funds on receipt of a deliverable). And since these contracts are deployed on a blockchain, there is no 3rd-party involved and transactions remain transparent and verifiable by anyone.
  
== Smart Contracts on Ethereum using Solidity ==
+
= Smart Contracts on Ethereum using Solidity =
 
Ethereum was the first blockchain to support the execution of smart contracts through an EVM (Ethereum Virtual Machine). Since then, many blockchains that have come up (like EOS, Hyperledger, etc) support smart contracts. For this tutorial, we stick to smart contract development on Ethereum since Ethereum is currently the most widely used platform for DApps and getting some hands-on knowledge on Ethereum will aid in understanding other blockchains as well.
 
Ethereum was the first blockchain to support the execution of smart contracts through an EVM (Ethereum Virtual Machine). Since then, many blockchains that have come up (like EOS, Hyperledger, etc) support smart contracts. For this tutorial, we stick to smart contract development on Ethereum since Ethereum is currently the most widely used platform for DApps and getting some hands-on knowledge on Ethereum will aid in understanding other blockchains as well.
  
 
The language used for coding smart contracts on Ethereum is [https://solidity.readthedocs.io/en/v0.5.9/introduction-to-smart-contracts.html '''Solidity''']. It is fairly similar to Javascript with minor changes in the syntax and keywords.
 
The language used for coding smart contracts on Ethereum is [https://solidity.readthedocs.io/en/v0.5.9/introduction-to-smart-contracts.html '''Solidity''']. It is fairly similar to Javascript with minor changes in the syntax and keywords.
 +
 +
To learn coding in Solidity, [https://learnxinyminutes.com/docs/solidity/ here is an amazing tutorial].
  
 
You can install the Solidity compiler using <code>npm install -g solc</code> (The <code>-g</code> is for installation globally, and not limited to a particular directory).
 
You can install the Solidity compiler using <code>npm install -g solc</code> (The <code>-g</code> is for installation globally, and not limited to a particular directory).
Line 15: Line 17:
 
[In case you get errors, check whether you have Node.js and Node Package Manager (npm) installed or not. If not, follow the instructions [https://nodejs.org/en/download/ here] to install them]
 
[In case you get errors, check whether you have Node.js and Node Package Manager (npm) installed or not. If not, follow the instructions [https://nodejs.org/en/download/ here] to install them]
  
== How to Follow Along... ==
+
= How to Follow Along... =
 
* We will need '''Truffle''', the most popular development framework for Ethereum. It can be installed using <code>npm install -g truffle </code>
 
* We will need '''Truffle''', the most popular development framework for Ethereum. It can be installed using <code>npm install -g truffle </code>
 
* Next, we need a ''TestRPC''. Now, what the hell is that? Ethereum TestRPC is a fast and customizable blockchain emulator. It allows making calls to the blockchain without the overheads of running an actual Ethereum node. For this, we install '''Ganache CLI''' which can spin up a personal blockchain locally for development purposes using <code>npm install -g ganache-cli</code>
 
* Next, we need a ''TestRPC''. Now, what the hell is that? Ethereum TestRPC is a fast and customizable blockchain emulator. It allows making calls to the blockchain without the overheads of running an actual Ethereum node. For this, we install '''Ganache CLI''' which can spin up a personal blockchain locally for development purposes using <code>npm install -g ganache-cli</code>
* Now, we need a Javascript API called '''web3.js''', which is used to connect to a blockchain node (here, the Ganache TestRPC) so that we could interact with it. This can be installed using <code>npm install -g web3</code>  
+
* Now, we need a Javascript API called '''web3.js''', which is used to connect to a blockchain node (here, the Ganache TestRPC) so that we could interact with it. This can be installed using <code>npm install -g web3</code>
 +
 
 +
== Remix - An Elegant Alternative ==
 +
In case you have issues in setting up Truffle or Ganache, you need not worry! There is an elegant alternative online interface that allows you to write, debug, deploy & test contracts in a very simple manner known as [https://remix.ethereum.org/ Remix].
 +
 
 +
''Note: For beginners, Remix will prove to be the go-to solution for developing smart contracts due to its user friendliness & hassle-free development environment setup''
 +
 
 +
* Remix provides you with an online interface where you can write your smart contracts [It also has some basic pre-written contracts in the File Explorer section. Feel free to check them out].
 +
* You can add several plugins that are found in the Plugin Manager [in the leftmost pane (the plug symbol)]
 +
* For this tutorial, you will need the "SOLIDITY COMPILER" and "DEPLOY & RUN TRANSACTIONS" plugins activated
 +
* After completing the smart contract, you could go to the SOLIDITY COMPILER, select the compiler version & click compile. You may also want to select "Auto compile" which will keep trying to compile your smart contract as you continue coding (this enables to catch & fix syntactic bugs in the code immediately as they appear)
 +
* Once compiled, you can go to DEPLOY & RUN TRANSACTIONS, select JavaScript VM as the environment (this runs a node through the browser, similar to Ganache, and provides you with 15 development addresses, each with 100 ETH), select the appropriate contract & deploy it.
 +
* Once deployed, you can find the contract under "Deployed Contracts". Click on that the contract to view the available functions that you can interact with and start using them
 +
 
 +
You can find the detailed documentation for using Remix IDE [https://remix.readthedocs.io/en/stable/ here].
 +
 
 +
''Once you are familiar with using Remix, try to understand the use the DEBUGGER plugin to investigate runtime bugs that are caught only when a transaction is tried to be executed. It provides an elaborate interface to go step by step through all operations (assembly level) performed while executing the function(s) involved in the smart contract.''
  
== Setting Up Your Environment ==
+
= Setting Up Your Environment =
 
'''Note: '''The steps mentioned below are strictly for Linux and Mac OS. In case you are using Windows, you may run into some issues at times. If so, do not hesitate to check out [https://stackoverflow.com/ stackoverflow] for assistance.
 
'''Note: '''The steps mentioned below are strictly for Linux and Mac OS. In case you are using Windows, you may run into some issues at times. If so, do not hesitate to check out [https://stackoverflow.com/ stackoverflow] for assistance.
  
Line 48: Line 66:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Creating Your  Smart Contract ==
+
= Writing Your  Smart Contract =
 
Go to the <code>contracts</code> folder, create a new file named <code>MyMarket.sol</code> and paste the following code into it (Don't worry if you do not understand anything in this code. We will go through it line-by-line):
 
Go to the <code>contracts</code> folder, create a new file named <code>MyMarket.sol</code> and paste the following code into it (Don't worry if you do not understand anything in this code. We will go through it line-by-line):
  
 
<syntaxhighlight lang="Javascript" line>
 
<syntaxhighlight lang="Javascript" line>
pragma solidity ^0.5.0;
+
pragma solidity ^0.6.0;
  
 
contract MyMarket {
 
contract MyMarket {
Line 135: Line 153:
  
 
         if (msg.sender.send(outstandingFundsAmount)) {
 
         if (msg.sender.send(outstandingFundsAmount)) {
 +
            _pendingWithdrawals[msg.sender] = 0;
 
             emit FundsPulled(msg.sender, outstandingFundsAmount);
 
             emit FundsPulled(msg.sender, outstandingFundsAmount);
 
             return true;
 
             return true;
Line 144: Line 163:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
[TODO: Add Explanation line-by-line for code above]
 
 
* First, the version pragma specifies what versions of Solidity a source file can work on. Here, it shows 0.5.0. This allows only <code>solc@0.5.0</code> to be able to compile this code.
 
* First, the version pragma specifies what versions of Solidity a source file can work on. Here, it shows 0.5.0. This allows only <code>solc@0.5.0</code> to be able to compile this code.
 
* The <code>contract</code> keyword is used to define a smart contract, followed by the contract name (here, <code>MyMarket</code>)
 
* The <code>contract</code> keyword is used to define a smart contract, followed by the contract name (here, <code>MyMarket</code>)
Line 152: Line 170:
 
* We now define an array of ''Items'' where we could add any item when a user to sell something. It is from here that the front end of a DApp would display the list of items available in the market. Note the use of <code>private</code> which implies that this array will only be visible for the contract that it declared in only (ie, this contract only).
 
* We now define an array of ''Items'' where we could add any item when a user to sell something. It is from here that the front end of a DApp would display the list of items available in the market. Note the use of <code>private</code> which implies that this array will only be visible for the contract that it declared in only (ie, this contract only).
 
* In line 25, we define a '''mapping''' (a set a key-value pairs; here, the key being of address type & value being of uint type) to store aggregated amounts that sellers would receive when their items are sold. ''[We will revisit this when we look at the pullFunds() function]''  
 
* In line 25, we define a '''mapping''' (a set a key-value pairs; here, the key being of address type & value being of uint type) to store aggregated amounts that sellers would receive when their items are sold. ''[We will revisit this when we look at the pullFunds() function]''  
 +
* Lines 27-30 define a '''modifier''', which creates additional features on function or apply some restriction on function. The modifier defined here checks that the seller of a particular item must not have the Null address, i.e. <code>address(0)</code>. Such assertions are made using <code>require(arg1, arg2)</code> statements, where <code>arg1</code> is the condition to be checked & <code>arg2</code> is the error message to be returned if the check fails.
 +
* Lines 32-45 are used to define a '''function''' to add a new item to the market & return the ID (index) of the item:
 +
** Functions must have [https://solidity.readthedocs.io/en/v0.4.21/contracts.html#visibility-and-getters ''visibility''] (such as <code>public</code> here)
 +
** Firstly, a new instance of the <code>struct Item</code> is created & stored in the <code>_items</code> array
 +
** Notice that the address of the issuer of the function call is obtained using <code>msg.sender</code>
 +
** Lastly, the <code>ItemAdded()</code> event is emitted [This is useful when you have a front-end, etc, integrated with this smart contract that subscribes to such events & triggers an action on hearing the event emission]
 +
* Lines 47-52 define the function for obtaining the details of an item using it's ID:
 +
** Notice that here we use our modifier defined previously to check that the seller of such an item exists in the market
 +
** The <code>view</code> keyword is used to say that the function promises not to modify the state of the contract. Since we are only reading the data in the smart contract here, this function is fit to be a view function. As a best practice, Getter methods are marked with <code>view</code>. To know more about <code>view</code> & <code>pure</code> head over to [https://solidity.readthedocs.io/en/v0.4.21/contracts.html#view-functions this link]. 
 +
* Lines 54-65 define the function used to buy an item from the market:
 +
** Here too, we use the function modifier. This is the advantage of writing modifiers for checks that need to be done in several functions. It reduces the repititive code that one would have written otherwise.
 +
** The <code>payable</code> keyword signifies that this function can be used to collect/receive funds in Ether. These funds can be accessed using <code>msg.value</code>. All the Ether sent to payable functions are owned by contract.
 +
** Here, we add the <code>msg.value</code> into the existing funds that the seller, after selling items, is yet to withdraw from the contract
 +
** Lastly, we emit the <code>ItemPurchased()</code> event to indicate the successful purchase of an item
 +
* Lines 67-76 define a function for removing an item that has been added to the market. The function is self-explanatory. [It is supposed to be called only by the seller of the item]
 +
* Lines 78-90 define a function for the seller to pull the funds that have been accumulated by sale of his items:
 +
** We first check that the existing funds that have not been withdrawn by the seller is non-zero
 +
** We then send this outstanding amount to the seller who has issued the function call by <code>msg.sender.send()</code>
 +
** Lastly, the <code>FundsPulled()</code> is emitted
  
Now use <code>truffle compile</code> to compile the contract. This will generate a <code>build</code> folder which contains the '''Application Binary Interface (ABI)''' of our contract. ABI is a data encoding scheme used in Ethereum for working with smart contracts.
+
Now use <code>truffle compile</code> to compile the contract. This will generate a <code>build/</code> folder which contains the '''Application Binary Interface (ABI)''' of our contract. ABI is a data encoding scheme used in Ethereum for working with smart contracts.
  
== Deploying Your Contract ==
+
= Deploying Your Contract =
  
 
Now we need to deploy our contract onto our local blockchain instance. For this, first fire up a new terminal and enter <code>ganache-cli</code>. This will automatically spin up a local blockchain that could be interacted with through <code>http://127.0.0.1:8545</code> by default. This also provides you with 10 test accounts, each with 100 ETH, which could be used for testing purposes. Let this run in the background, and we now go to the <code>migrations</code> folder and create a file <code>2_deploy_contracts.js</code>. Now paste the following into this file:
 
Now we need to deploy our contract onto our local blockchain instance. For this, first fire up a new terminal and enter <code>ganache-cli</code>. This will automatically spin up a local blockchain that could be interacted with through <code>http://127.0.0.1:8545</code> by default. This also provides you with 10 test accounts, each with 100 ETH, which could be used for testing purposes. Let this run in the background, and we now go to the <code>migrations</code> folder and create a file <code>2_deploy_contracts.js</code>. Now paste the following into this file:
Line 175: Line 212:
 
To deploy the contract to our local ganache instance, run <code>truffle migrate --network development</code>. You will see contracts getting deployed and their addresses being shown on the terminal.
 
To deploy the contract to our local ganache instance, run <code>truffle migrate --network development</code>. You will see contracts getting deployed and their addresses being shown on the terminal.
  
== Testing Your Contract ==
+
= Testing Your Contract =
  
Before making the contract available for everyone, we first need to test it for loopholes and ensure its smooth functioning in general. Even though it's often sidelined, testing is one of the most important aspects of developing any software in general. For making tests, create a folder named <code>test</code> and in that folder, create a new file named <code>test.js</code> and in that, copy-paste the code given below (Don't worry if you do not understand anything in this code. We will go through it line-by-line):
+
Before making the contract available for everyone, we first need to test it for loopholes and ensure its smooth functioning in general. Even though it's often sidelined, testing is one of the most important aspects of developing any software in general. For making tests, create a folder named <code>test</code> and in that folder, create a new file named <code>test.js</code> and in that, copy-paste the code given below (Don't worry if you do not understand anything in this code. We will go through it to understand the details):
 
<syntaxhighlight lang="Javascript" line>
 
<syntaxhighlight lang="Javascript" line>
 
var MyMarket = artifacts.require("./MyMarket.sol");
 
var MyMarket = artifacts.require("./MyMarket.sol");
Line 261: Line 298:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
[TODO: Add Explanation line-by-line for code above]
 
  
* First, we import dependencies and create instance of contract for testing.
+
* First, we import dependencies and create instance of contract for testing in the line <code>contractInstance = await MyMarket.deployed()</code>.
 
* Here, the important part are the lines starting with <code>assert.equal</code> It basically tests whether RHS value is equal to LHS value. If not the error message, which is given as the third argument, is displayed. You can add as many test cases as you want to check different aspects of your code in border cases.
 
* Here, the important part are the lines starting with <code>assert.equal</code> It basically tests whether RHS value is equal to LHS value. If not the error message, which is given as the third argument, is displayed. You can add as many test cases as you want to check different aspects of your code in border cases.
 
* Each task has its own set of test cases, and all the tasks have been categorized into success and failure states. Success states include the tasks that would make the changes according to the contract and the failure ones include tasks that break the contract.
 
* Each task has its own set of test cases, and all the tasks have been categorized into success and failure states. Success states include the tasks that would make the changes according to the contract and the failure ones include tasks that break the contract.
 +
* We see the use of <code>async</code> & <code>await</code> while defining functions for each test. Await function is used to wait for the promise. It could be used within the async block only. It makes the code wait until the promise returns a result. It only makes the async block wait.We use this paradigm of coding here because Javascript interacts with the smart contract (deployed on the blockchain) in the form of promises. It asynchronously receives the data that it has requested from the smart contract.
 +
* All details of a transaction can be found within <code>tx.receipt</code>
 +
* The success testing includes testing for:
 +
** Adding 2 items
 +
** Removing one of them
 +
** Buying one product
 +
* Failure testing includes trying to remove a non-existing product
 +
 +
''There can be many more tests that can be added in either sections to make the contract more robust. We encourage you to try writing your own tests for these which may include checking if the amount withdrawn by the seller is correctly transferred of not, trying to get the details of a non-existing product, trying to sell a product that is not owned by the seller, etc.''
 +
 +
= See Also =
 +
* [https://solidity.readthedocs.io/en/v0.6.8/index.html Solidity Documentation]
 +
* [https://cryptozombies.io/ Crypto Zombies - Learn to Code Blockchain DApps By Building Simple Games]
 +
* [https://hackernoon.com/ethereum-development-walkthrough-part-1-smart-contracts-b3979e6e573e Another tutorial about writing Smart Contracts on Ethereum]
 +
* [https://remix-ide.readthedocs.io/en/latest/tutorial_debug.html Debugging Smart Contracts with Remix]

Latest revision as of 19:41, 23 May 2020

What lies at the core of Decentralised Applications built on blockchains? How do I implement my business logic into a blockchain? What programming languages do I need to be familiar with? All these, and many more such questions would be answered through this article on writing and testing Smart Contracts on Ethereum. This tutorial focuses on the Solidity (the language for writing smart contracts), Javascript (for testing them) and some basic frameworks like Truffle (to deploy and interact with smart contracts) and Ganache (to spin up a local blockchain for testing).

What are Smart Contracts?

As per the definition by Investopedia, "Smart contracts are self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. The code and the agreements contained therein exist across a distributed, decentralized blockchain network. Smart contracts permit trusted transactions and agreements to be carried out among disparate, anonymous parties without the need for a central authority, legal system, or external enforcement mechanism. They render transactions traceable, transparent, and irreversible."

Smart Contracts form the crux of Decentralised Applications (DApps), aiding in implementing the business logic through codified contracts. When parties engaged in the contract fulfil certain obligations, the contract smartly knows what action to take (such as releasing funds on receipt of a deliverable). And since these contracts are deployed on a blockchain, there is no 3rd-party involved and transactions remain transparent and verifiable by anyone.

Smart Contracts on Ethereum using Solidity

Ethereum was the first blockchain to support the execution of smart contracts through an EVM (Ethereum Virtual Machine). Since then, many blockchains that have come up (like EOS, Hyperledger, etc) support smart contracts. For this tutorial, we stick to smart contract development on Ethereum since Ethereum is currently the most widely used platform for DApps and getting some hands-on knowledge on Ethereum will aid in understanding other blockchains as well.

The language used for coding smart contracts on Ethereum is Solidity. It is fairly similar to Javascript with minor changes in the syntax and keywords.

To learn coding in Solidity, here is an amazing tutorial.

You can install the Solidity compiler using npm install -g solc (The -g is for installation globally, and not limited to a particular directory).

[In case you get errors, check whether you have Node.js and Node Package Manager (npm) installed or not. If not, follow the instructions here to install them]

How to Follow Along...

  • We will need Truffle, the most popular development framework for Ethereum. It can be installed using npm install -g truffle
  • Next, we need a TestRPC. Now, what the hell is that? Ethereum TestRPC is a fast and customizable blockchain emulator. It allows making calls to the blockchain without the overheads of running an actual Ethereum node. For this, we install Ganache CLI which can spin up a personal blockchain locally for development purposes using npm install -g ganache-cli
  • Now, we need a Javascript API called web3.js, which is used to connect to a blockchain node (here, the Ganache TestRPC) so that we could interact with it. This can be installed using npm install -g web3

Remix - An Elegant Alternative

In case you have issues in setting up Truffle or Ganache, you need not worry! There is an elegant alternative online interface that allows you to write, debug, deploy & test contracts in a very simple manner known as Remix.

Note: For beginners, Remix will prove to be the go-to solution for developing smart contracts due to its user friendliness & hassle-free development environment setup

  • Remix provides you with an online interface where you can write your smart contracts [It also has some basic pre-written contracts in the File Explorer section. Feel free to check them out].
  • You can add several plugins that are found in the Plugin Manager [in the leftmost pane (the plug symbol)]
  • For this tutorial, you will need the "SOLIDITY COMPILER" and "DEPLOY & RUN TRANSACTIONS" plugins activated
  • After completing the smart contract, you could go to the SOLIDITY COMPILER, select the compiler version & click compile. You may also want to select "Auto compile" which will keep trying to compile your smart contract as you continue coding (this enables to catch & fix syntactic bugs in the code immediately as they appear)
  • Once compiled, you can go to DEPLOY & RUN TRANSACTIONS, select JavaScript VM as the environment (this runs a node through the browser, similar to Ganache, and provides you with 15 development addresses, each with 100 ETH), select the appropriate contract & deploy it.
  • Once deployed, you can find the contract under "Deployed Contracts". Click on that the contract to view the available functions that you can interact with and start using them

You can find the detailed documentation for using Remix IDE here.

Once you are familiar with using Remix, try to understand the use the DEBUGGER plugin to investigate runtime bugs that are caught only when a transaction is tried to be executed. It provides an elaborate interface to go step by step through all operations (assembly level) performed while executing the function(s) involved in the smart contract.

Setting Up Your Environment

Note: The steps mentioned below are strictly for Linux and Mac OS. In case you are using Windows, you may run into some issues at times. If so, do not hesitate to check out stackoverflow for assistance.

  1. Create a directory for this project, and move into it.
  2. Use truffle init to allow Truffle to initialise the directory with the necessary files and folders.
  3. Use npm init to initialise this directory as an npm package as well. Fill in the required details during this initialisation. (If you do not understand something, leave it as default)
  4. Go to the truffle-config.js file and uncomment the following within networks:
45 // development: {
46     //  host: "127.0.0.1",     // Localhost (default: none)
47     //  port: 8545,            // Standard Ethereum port (default: none)
48     //  network_id: "*",       // Any network (default: none)
49     // }

This is done because the local Ganache instance will use port 8545 by default and could be referenced to by "development" network.

Now your directory tree would look something like this:

contracts/
 |-- Migrations.sol
migrations/
 |-- 1_initial_migration.js
package.json
test/
truffle-config.js

Writing Your Smart Contract

Go to the contracts folder, create a new file named MyMarket.sol and paste the following code into it (Don't worry if you do not understand anything in this code. We will go through it line-by-line):

 1 pragma solidity ^0.6.0;
 2 
 3 contract MyMarket {
 4 
 5     // Track the state of the items, while preserving history
 6     enum ItemStatus {
 7         active,
 8         sold,
 9         removed
10     }
11 
12     struct Item {
13         string name;
14         uint price;
15         address seller;
16         ItemStatus status;
17     }
18 
19     event ItemAdded(string name, uint price, address seller);
20     event ItemPurchased(uint itemID, address buyer, address seller);
21     event ItemRemoved(uint itemID);
22     event FundsPulled(address owner, uint amount);
23 
24     Item[] private _items;
25     mapping (address => uint) public _pendingWithdrawals;
26 
27     modifier onlyIfItemExists(uint itemID) {
28         require(_items[itemID].seller != address(0), "Item seller does not exist");
29         _;
30     }
31 
32     function addNewItem(string memory name, uint price) public returns (uint) {
33 
34         _items.push(Item({
35             name: name,
36             price: price,
37             seller: msg.sender,
38             status: ItemStatus.active
39         }));
40 
41         emit ItemAdded(name, price, msg.sender);
42         // Item is pushed to the end, so the lenth is used for
43         // the ID of the item
44         return _items.length - 1;
45     }
46 
47     function getItem(uint itemID) public view onlyIfItemExists(itemID)
48     returns (string memory, uint, address, uint) {
49 
50         Item storage item = _items[itemID];
51         return (item.name, item.price, item.seller, uint(item.status));
52     }
53 
54     function buyItem(uint itemID) public payable onlyIfItemExists(itemID) {
55 
56         Item storage currentItem = _items[itemID];
57 
58         require(currentItem.status == ItemStatus.active, "Item is not available");
59         require(currentItem.price == msg.value, "Incorrect amount sent");
60 
61         _pendingWithdrawals[currentItem.seller] = msg.value;
62         currentItem.status = ItemStatus.sold;
63 
64         emit ItemPurchased(itemID, msg.sender, currentItem.seller);
65     }
66 
67     function removeItem(uint itemID) public onlyIfItemExists(itemID) {
68         Item storage currentItem = _items[itemID];
69 
70         require(currentItem.seller == msg.sender, "You are not the seller of this product");
71         require(currentItem.status == ItemStatus.active, "This item has already been purchased or removed");
72 
73         currentItem.status = ItemStatus.removed;
74 
75         emit ItemRemoved(itemID);
76     }
77 
78     function pullFunds() public returns (bool) {
79         require(_pendingWithdrawals[msg.sender] > 0, "No pending withdrawals");
80 
81         uint outstandingFundsAmount = _pendingWithdrawals[msg.sender];
82 
83         if (msg.sender.send(outstandingFundsAmount)) {
84             _pendingWithdrawals[msg.sender] = 0;
85             emit FundsPulled(msg.sender, outstandingFundsAmount);
86             return true;
87         } else {
88             return false;
89         }
90     }
91 }
  • First, the version pragma specifies what versions of Solidity a source file can work on. Here, it shows 0.5.0. This allows only solc@0.5.0 to be able to compile this code.
  • The contract keyword is used to define a smart contract, followed by the contract name (here, MyMarket)
  • We first define an enumeration to store the status of a product in the market.
  • Now, we define a structure (user-defined datatype) to store details of "Items" in the market. Note the different datatypes used to store the different parameters: string to store the item name, uint (unsigned integer) to store the price, address (stores Ethereum Addresses that look something like 0xc7031b6afa7972560618ba7ccf2ae099ae5c393f) to store the seller of the product and the enumeration ItemStatus to store the current status of the item.
  • Next, you would see some events being declared. Events are a way for smart contracts written in Solidity to log that something has occurred. Logging an event is done using the emit keyword. JavaScript front ends for DApps can watch for events and react accordingly. By convention, event names begin with uppercase letters. To know more details about events, visit this page.
  • We now define an array of Items where we could add any item when a user to sell something. It is from here that the front end of a DApp would display the list of items available in the market. Note the use of private which implies that this array will only be visible for the contract that it declared in only (ie, this contract only).
  • In line 25, we define a mapping (a set a key-value pairs; here, the key being of address type & value being of uint type) to store aggregated amounts that sellers would receive when their items are sold. [We will revisit this when we look at the pullFunds() function]
  • Lines 27-30 define a modifier, which creates additional features on function or apply some restriction on function. The modifier defined here checks that the seller of a particular item must not have the Null address, i.e. address(0). Such assertions are made using require(arg1, arg2) statements, where arg1 is the condition to be checked & arg2 is the error message to be returned if the check fails.
  • Lines 32-45 are used to define a function to add a new item to the market & return the ID (index) of the item:
    • Functions must have visibility (such as public here)
    • Firstly, a new instance of the struct Item is created & stored in the _items array
    • Notice that the address of the issuer of the function call is obtained using msg.sender
    • Lastly, the ItemAdded() event is emitted [This is useful when you have a front-end, etc, integrated with this smart contract that subscribes to such events & triggers an action on hearing the event emission]
  • Lines 47-52 define the function for obtaining the details of an item using it's ID:
    • Notice that here we use our modifier defined previously to check that the seller of such an item exists in the market
    • The view keyword is used to say that the function promises not to modify the state of the contract. Since we are only reading the data in the smart contract here, this function is fit to be a view function. As a best practice, Getter methods are marked with view. To know more about view & pure head over to this link.
  • Lines 54-65 define the function used to buy an item from the market:
    • Here too, we use the function modifier. This is the advantage of writing modifiers for checks that need to be done in several functions. It reduces the repititive code that one would have written otherwise.
    • The payable keyword signifies that this function can be used to collect/receive funds in Ether. These funds can be accessed using msg.value. All the Ether sent to payable functions are owned by contract.
    • Here, we add the msg.value into the existing funds that the seller, after selling items, is yet to withdraw from the contract
    • Lastly, we emit the ItemPurchased() event to indicate the successful purchase of an item
  • Lines 67-76 define a function for removing an item that has been added to the market. The function is self-explanatory. [It is supposed to be called only by the seller of the item]
  • Lines 78-90 define a function for the seller to pull the funds that have been accumulated by sale of his items:
    • We first check that the existing funds that have not been withdrawn by the seller is non-zero
    • We then send this outstanding amount to the seller who has issued the function call by msg.sender.send()
    • Lastly, the FundsPulled() is emitted

Now use truffle compile to compile the contract. This will generate a build/ folder which contains the Application Binary Interface (ABI) of our contract. ABI is a data encoding scheme used in Ethereum for working with smart contracts.

Deploying Your Contract

Now we need to deploy our contract onto our local blockchain instance. For this, first fire up a new terminal and enter ganache-cli. This will automatically spin up a local blockchain that could be interacted with through http://127.0.0.1:8545 by default. This also provides you with 10 test accounts, each with 100 ETH, which could be used for testing purposes. Let this run in the background, and we now go to the migrations folder and create a file 2_deploy_contracts.js. Now paste the following into this file:

1 let MyMarket = artifacts.require("./MyMarket.sol");
2 
3 module.exports = function(deployer) {
4   deployer.deploy(MyMarket);
5 };

Migrations are basically Javascript files that help you deploy contracts to the Ethereum network. To know more about them, visit this page.

artifacts basically store the ABI, binary of contract and other info related to the contracts that are compiled. In line 1, we store the details of MyMarket.sol contract in a variable called MyMarket.

Now, we use the deployer to deploy this contract to our blockchain network when it is called.

To deploy the contract to our local ganache instance, run truffle migrate --network development. You will see contracts getting deployed and their addresses being shown on the terminal.

Testing Your Contract

Before making the contract available for everyone, we first need to test it for loopholes and ensure its smooth functioning in general. Even though it's often sidelined, testing is one of the most important aspects of developing any software in general. For making tests, create a folder named test and in that folder, create a new file named test.js and in that, copy-paste the code given below (Don't worry if you do not understand anything in this code. We will go through it to understand the details):

 1 var MyMarket = artifacts.require("./MyMarket.sol");
 2 const truffleAssert = require('truffle-assertions');
 3 
 4 var contractInstance;
 5 var itemID;
 6 
 7 
 8 
 9 contract("MyMarket", async function(accounts){
10     before(async () => {
11         contractInstance = await MyMarket.deployed();
12     })
13     describe("success states", async () => {
14         
15         it("should add product 1", async () => {
16             var name = "Prod1";
17             var price = 200;
18             var itemSeller = accounts[0];
19     
20             
21             itemID = await contractInstance.addNewItem.call(name, price, {from: itemSeller});
22             var tx = contractInstance.addNewItem(name, price, {from: itemSeller});
23             // console.log(itemID.toNumber())
24             var res = await contractInstance.getItem.call(itemID.toNumber())
25     
26             assert.equal(name, res["0"], "Name wasn't added properly");
27             assert.equal(price, res["1"].toNumber(), "Price wasn't added properly");
28             assert.equal(itemSeller, res["2"], "Seller wasn't added properly");
29             assert.equal(res["3"].toNumber(), 0, "Status wasn't added properly");
30     
31         
32         });
33     
34         it("should add product 2", async () => {
35             var name = "Prod2";
36             var price = 100;
37             var itemSeller = accounts[0];
38     
39             
40             itemID = await contractInstance.addNewItem.call(name, price, {from: itemSeller});
41             var tx = contractInstance.addNewItem(name, price, {from: itemSeller});
42             // console.log(itemID.toNumber())
43             var res = await contractInstance.getItem.call(itemID.toNumber())
44     
45             assert.equal(name, res["0"], "Name wasn't added properly");
46             assert.equal(price, res["1"].toNumber(), "Price wasn't added properly");
47             assert.equal(itemSeller, res["2"], "Seller wasn't added properly");
48             assert.equal(res["3"].toNumber(), 0, "Status wasn't added properly");
49     
50         
51         });
52     
53         it("should remove product 1", async () => {
54             itemID = 0;
55             var tx = await contractInstance.removeItem(itemID);
56             var removedId = tx.receipt.logs[0].args["0"].toNumber()
57             assert.equal(removedId, itemID, "Incorrect item removed")
58             var res = await contractInstance.getItem.call(itemID)
59             assert.equal(res["3"].toNumber(), 2, "Item hasn't been removed correctly");
60             // console.log(res);
61         });
62     
63         it("should buy product 2", async () => {
64             itemID = 1;
65             var tx = await contractInstance.buyItem(itemID, {from: accounts[1], value: 100});
66             var boughtId = tx.receipt.logs[0].args["0"].toNumber()
67             assert.equal(boughtId, itemID, "Incorrect item bought")
68             var res = await contractInstance.getItem.call(itemID)
69             assert.equal(res["3"].toNumber(), 1, "Item hasn't been bought correctly");
70         });
71     })
72 
73     describe("failure states", async () => {
74         it("should not remove product 2", async () => {
75             itemID = 1;
76             await truffleAssert.reverts(contractInstance.removeItem(itemID), "This item has already been purchased or removed");
77         });
78     })
79     
80 });


  • First, we import dependencies and create instance of contract for testing in the line contractInstance = await MyMarket.deployed().
  • Here, the important part are the lines starting with assert.equal It basically tests whether RHS value is equal to LHS value. If not the error message, which is given as the third argument, is displayed. You can add as many test cases as you want to check different aspects of your code in border cases.
  • Each task has its own set of test cases, and all the tasks have been categorized into success and failure states. Success states include the tasks that would make the changes according to the contract and the failure ones include tasks that break the contract.
  • We see the use of async & await while defining functions for each test. Await function is used to wait for the promise. It could be used within the async block only. It makes the code wait until the promise returns a result. It only makes the async block wait.We use this paradigm of coding here because Javascript interacts with the smart contract (deployed on the blockchain) in the form of promises. It asynchronously receives the data that it has requested from the smart contract.
  • All details of a transaction can be found within tx.receipt
  • The success testing includes testing for:
    • Adding 2 items
    • Removing one of them
    • Buying one product
  • Failure testing includes trying to remove a non-existing product

There can be many more tests that can be added in either sections to make the contract more robust. We encourage you to try writing your own tests for these which may include checking if the amount withdrawn by the seller is correctly transferred of not, trying to get the details of a non-existing product, trying to sell a product that is not owned by the seller, etc.

See Also