Akash Mittal I am a software engineer and a die-hard animal lover. My life's goal is to create a mini jungle with a dispensary for stray animals who get diseased or injured.

Truffle Suite tutorial: How to develop Ethereum smart contracts

11 min read 3334

Truffle Logo

Ever wondered how to create your own blockchain application? When it comes to Ethereum, it starts with smart contracts.

In this tutorial, we’ll focus on smart contracts written in Solidity language. We’ll use the Truffle Suite to deploy a local version of the Ethereum blockchain and compile smart contracts using Ethereum Virtual Machine (EVM).

If you’re not familiar with the technologies described above, I would recommend first reading our guide to Ethereum blockchain development using Web3.js.

What is the Truffle Suite?

Ethereum is a blockchain that allows applications to run on it. The code is written in Solidity language in the form of smart contracts. To compile these contracts, we need an Ethereum compiler, which converts smart contracts to machine-readable code.

The Truffle Suite is a collection of tools made specifically for blockchain development on Ethereum. The suite includes three pieces of software:

  1. Truffle, a framework for smart contract development
  2. Ganache, which enables you to set a personal Ethereum blockchain on the local network for testing and development
  3. Drizzle, which is used for creating DApp user interfaces and includes a collection of ready-to-use components

Setting up libraries and software

For this tutorial, we need a few node packages and software, including the Ethereum blockchain, smart contract compiler, and a JavaScript library for communication.

Installing Ganache

Smart contracts run on the Ethereum blockchain, so we require one for deployment and testing. We could also deploy on a live chain, but that would cost us Ether as a gas fee. So let’s set up a local chain and do our testing there. When you’re sure about the code and ready to distribute your application then you can deploy on live chain.

Ganache is the local chain that gets installed on our computers and runs on localhost. Download and install Ganache from the Truffle Suite website.

Truffle Suite Website

You can see that Ganache provided 10 accounts with 100 ETH each. These are fake Ethers, so don’t get excited. Also, the chain is running on 127.0.0.1 at 7545 port. We will use these accounts to deploy our smart contracts on this chain. Ethers will help us pay gas fees.

We made a custom demo for .
No really. Click here to check it out.

Installing Node.js

We’ll need Node.js for this project, so make sure it’s installed on your system. You can download it from the Node.js official website.

Installing Web3.js

Web3.js is a JavaScript library that enables communication with the Ethereum blockchain through JavaScript. It has a lot of inbuilt functions that are useful in development.

You can install Web3.js using npm or yarn:

npm install web3 -g

Installing Truffle

Truffle provides the compiler for smart contracts. We need it to convert the Solidity code into machine-readable code that can be deployed on Ganache blockchain.

Install Truffle using the following command:

npm install truffle -g

Creating smart contracts

To create smart contracts, we first need to create a project directory where we will keep all the Solidity files. Let’s create one with the name solidity and move to the directory in the terminal using cd solidity.

Right now, our project is empty. To work with it, we need some boilerplate code. For example, if you wish to create the UI in React, you’ll need to install React.

Truffle already provides some packages, which are called boxes. These packages are bundles of different frameworks, such as Truffle, Ganache, React, Web3, and Redux, and there is one for Vue.js developers. Together, they complete the end-to-end application development, from client UI to blockchain smart contracts.

In this article, we’ll use the React box provided by Truffle.

Installing the React box

To install the React box, run the following command:

truffle unbox react

This will install Web3.js, React, Ganache CLI, Truffle, and Ethereum. In a sense, we didn’t really need to install all these utilities in the first step.

For the purpose of this tutorial, we won’t focus on React or a browser-based UI. Instead, we’ll create the smart contracts and handle them with the terminal only.

The directory structure of our project will look like this:

Directory Structure

Here, client is a React project folder where we can create the UI of the application. There is a folder inside it (client/src/contracts) that holds the compiled smart contracts in JSON format. These files are generated when we compile our smart contracts. They contain the ABI, bytecode, and other information.

Two JSON Files

From the image above, you can see that we have two JSON files. That’s because Truffle already provided two smart contracts when we unboxed the React bundle. If you open any of them, you will find code like this:

{
  "contractName": "SimpleStorage",
  "abi": [
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "x",
          "type": "uint256"
        }
      ],
      "name": "set",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [],
      "name": "get",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    }
  ],
  "metadata": "{\"compiler\":{\"version\":\"0.5.16+commit.9c3226ce\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"project:/contracts/SimpleStorage.sol\":\"SimpleStorage\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"project:/contracts/SimpleStorage.sol\":{\"keccak256\":\"0x512df1603c5f878921707d236bc53d974afe05b4d9de4b6094249bac5ab60efe\",\"urls\":[\"bzz-raw://0d6de97971b1c387f984fa7ea1d9ec10f8a63d68cc63bf8bd00d8c3a7c9e3ee1\",\"dweb:/ipfs/Qmbt92T34sHzedfJjDsvbisvLhRtghNwS6VW8tqrGkrqTD\"]}},\"version\":1}",
  "bytecode": "0x608060405234801561001057600080fd5b5060c68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a7231582044ccb2c2d46346d523107088f3e26a4c8a2ec3ec8b2e3a6edb1bc8574d5c5f5264736f6c63430005100032",
  "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a7231582044ccb2c2d46346d523107088f3e26a4c8a2ec3ec8b2e3a6edb1bc8574d5c5f5264736f6c63430005100032",
  "sourceMap": "66:176:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:176:1;;;;;;;",
  "deployedSourceMap": "66:176:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:176:1;;;;;;;;;;;;;;;;;;;;;;;;113:53;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;113:53:1;;;;;;;;;;;;;;;;;:::i;:::-;;170:70;;;:::i;:::-;;;;;;;;;;;;;;;;;;;113:53;160:1;147:10;:14;;;;113:53;:::o;170:70::-;206:4;225:10;;218:17;;170:70;:::o",
  "source": "// SPDX-License-Identifier: MIT\npragma solidity >=0.4.21 <0.7.0;\n\ncontract SimpleStorage {\n  uint storedData;\n\n  function set(uint x) public {\n    storedData = x;\n  }\n\n  function get() public view returns (uint) {\n    return storedData;\n  }\n}\n",
  "sourcePath": "C:\\Users\\akash\\Desktop\\solidity\\contracts\\SimpleStorage.sol",
  "ast": {
    "absolutePath": "project:/contracts/SimpleStorage.sol",
    "exportedSymbols": {
      "SimpleStorage": [
        59
      ]
    },
    "id": 60,
    "nodeType": "SourceUnit",
    "nodes": [
      {
        "id": 38,
        "literals": [
          "solidity",
          ">=",
          "0.4",
          ".21",
          "<",
          "0.7",
          ".0"
        ],
        "nodeType": "PragmaDirective",
        "src": "32:32:1"
      },
      {
        "baseContracts": [],
        "contractDependencies": [],
        "contractKind": "contract",
        "documentation": null,
        "fullyImplemented": true,
        "id": 59,
        "linearizedBaseContracts": [
          59
        ],
        "name": "SimpleStorage",
        "nodeType": "ContractDefinition",
        "nodes": [
          {
            "constant": false,
            "id": 40,
            "name": "storedData",
            "nodeType": "VariableDeclaration",
            "scope": 59,
            "src": "93:15:1",
            "stateVariable": true,
            "storageLocation": "default",
            "typeDescriptions": {
              "typeIdentifier": "t_uint256",
              "typeString": "uint256"
            },
            "typeName": {
              "id": 39,
              "name": "uint",
              "nodeType": "ElementaryTypeName",
              "src": "93:4:1",
              "typeDescriptions": {
                "typeIdentifier": "t_uint256",
                "typeString": "uint256"
              }
            },
            "value": null,
            "visibility": "internal"
          },
          {
            "body": {
              "id": 49,
              "nodeType": "Block",
              "src": "141:25:1",
              "statements": [
                {
                  "expression": {
                    "argumentTypes": null,
                    "id": 47,
                    "isConstant": false,
                    "isLValue": false,
                    "isPure": false,
                    "lValueRequested": false,
                    "leftHandSide": {
                      "argumentTypes": null,
                      "id": 45,
                      "name": "storedData",
                      "nodeType": "Identifier",
                      "overloadedDeclarations": [],
                      "referencedDeclaration": 40,
                      "src": "147:10:1",
                      "typeDescriptions": {
                        "typeIdentifier": "t_uint256",
                        "typeString": "uint256"
                      }
                    },
                    "nodeType": "Assignment",
                    "operator": "=",
                    "rightHandSide": {
                      "argumentTypes": null,
                      "id": 46,
                      "name": "x",
                      "nodeType": "Identifier",
                      "overloadedDeclarations": [],
                      "referencedDeclaration": 42,
                      "src": "160:1:1",
                      "typeDescriptions": {
                        "typeIdentifier": "t_uint256",
                        "typeString": "uint256"
                      }
                    },
                    "src": "147:14:1",
                    "typeDescriptions": {
                      "typeIdentifier": "t_uint256",
                      "typeString": "uint256"
                    }
                  },
                  "id": 48,
                  "nodeType": "ExpressionStatement",
                  "src": "147:14:1"
                }
              ]
            },
            "documentation": null,
            "id": 50,
            "implemented": true,
            "kind": "function",
            "modifiers": [],
            "name": "set",
            "nodeType": "FunctionDefinition",
            "parameters": {
              "id": 43,
              "nodeType": "ParameterList",
              "parameters": [
                {
                  "constant": false,
                  "id": 42,
                  "name": "x",
                  "nodeType": "VariableDeclaration",
                  "scope": 50,
                  "src": "126:6:1",
                  "stateVariable": false,
                  "storageLocation": "default",
                  "typeDescriptions": {
                    "typeIdentifier": "t_uint256",
                    "typeString": "uint256"
                  },
                  "typeName": {
                    "id": 41,
                    "name": "uint",
                    "nodeType": "ElementaryTypeName",
                    "src": "126:4:1",
                    "typeDescriptions": {
                      "typeIdentifier": "t_uint256",
                      "typeString": "uint256"
                    }
                  },
                  "value": null,
                  "visibility": "internal"
                }
              ],
              "src": "125:8:1"
            },
            "returnParameters": {
              "id": 44,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "141:0:1"
            },
            "scope": 59,
            "src": "113:53:1",
            "stateMutability": "nonpayable",
            "superFunction": null,
            "visibility": "public"
          },
          {
            "body": {
              "id": 57,
              "nodeType": "Block",
              "src": "212:28:1",
              "statements": [
                {
                  "expression": {
                    "argumentTypes": null,
                    "id": 55,
                    "name": "storedData",
                    "nodeType": "Identifier",
                    "overloadedDeclarations": [],
                    "referencedDeclaration": 40,
                    "src": "225:10:1",
                    "typeDescriptions": {
                      "typeIdentifier": "t_uint256",
                      "typeString": "uint256"
                    }
                  },
                  "functionReturnParameters": 54,
                  "id": 56,
                  "nodeType": "Return",
                  "src": "218:17:1"
                }
              ]
            },
            "documentation": null,
            "id": 58,
            "implemented": true,
            "kind": "function",
            "modifiers": [],
            "name": "get",
            "nodeType": "FunctionDefinition",
            "parameters": {
              "id": 51,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "182:2:1"
            },
            "returnParameters": {
              "id": 54,
              "nodeType": "ParameterList",
              "parameters": [
                {
                  "constant": false,
                  "id": 53,
                  "name": "",
                  "nodeType": "VariableDeclaration",
                  "scope": 58,
                  "src": "206:4:1",
                  "stateVariable": false,
                  "storageLocation": "default",
                  "typeDescriptions": {
                    "typeIdentifier": "t_uint256",
                    "typeString": "uint256"
                  },
                  "typeName": {
                    "id": 52,
                    "name": "uint",
                    "nodeType": "ElementaryTypeName",
                    "src": "206:4:1",
                    "typeDescriptions": {
                      "typeIdentifier": "t_uint256",
                      "typeString": "uint256"
                    }
                  },
                  "value": null,
                  "visibility": "internal"
                }
              ],
              "src": "205:6:1"
            },
            "scope": 59,
            "src": "170:70:1",
            "stateMutability": "view",
            "superFunction": null,
            "visibility": "public"
          }
        ],
        "scope": 60,
        "src": "66:176:1"
      }
    ],
    "src": "32:211:1"
  },
  "legacyAST": {
    "attributes": {
      "absolutePath": "project:/contracts/SimpleStorage.sol",
      "exportedSymbols": {
        "SimpleStorage": [
          59
        ]
      }
    },
    "children": [
      {
        "attributes": {
          "literals": [
            "solidity",
            ">=",
            "0.4",
            ".21",
            "<",
            "0.7",
            ".0"
          ]
        },
        "id": 38,
        "name": "PragmaDirective",
        "src": "32:32:1"
      },
      {
        "attributes": {
          "baseContracts": [
            null
          ],
          "contractDependencies": [
            null
          ],
          "contractKind": "contract",
          "documentation": null,
          "fullyImplemented": true,
          "linearizedBaseContracts": [
            59
          ],
          "name": "SimpleStorage",
          "scope": 60
        },
        "children": [
          {
            "attributes": {
              "constant": false,
              "name": "storedData",
              "scope": 59,
              "stateVariable": true,
              "storageLocation": "default",
              "type": "uint256",
              "value": null,
              "visibility": "internal"
            },
            "children": [
              {
                "attributes": {
                  "name": "uint",
                  "type": "uint256"
                },
                "id": 39,
                "name": "ElementaryTypeName",
                "src": "93:4:1"
              }
            ],
            "id": 40,
            "name": "VariableDeclaration",
            "src": "93:15:1"
          },
          {
            "attributes": {
              "documentation": null,
              "implemented": true,
              "isConstructor": false,
              "kind": "function",
              "modifiers": [
                null
              ],
              "name": "set",
              "scope": 59,
              "stateMutability": "nonpayable",
              "superFunction": null,
              "visibility": "public"
            },
            "children": [
              {
                "children": [
                  {
                    "attributes": {
                      "constant": false,
                      "name": "x",
                      "scope": 50,
                      "stateVariable": false,
                      "storageLocation": "default",
                      "type": "uint256",
                      "value": null,
                      "visibility": "internal"
                    },
                    "children": [
                      {
                        "attributes": {
                          "name": "uint",
                          "type": "uint256"
                        },
                        "id": 41,
                        "name": "ElementaryTypeName",
                        "src": "126:4:1"
                      }
                    ],
                    "id": 42,
                    "name": "VariableDeclaration",
                    "src": "126:6:1"
                  }
                ],
                "id": 43,
                "name": "ParameterList",
                "src": "125:8:1"
              },
              {
                "attributes": {
                  "parameters": [
                    null
                  ]
                },
                "children": [],
                "id": 44,
                "name": "ParameterList",
                "src": "141:0:1"
              },
              {
                "children": [
                  {
                    "children": [
                      {
                        "attributes": {
                          "argumentTypes": null,
                          "isConstant": false,
                          "isLValue": false,
                          "isPure": false,
                          "lValueRequested": false,
                          "operator": "=",
                          "type": "uint256"
                        },
                        "children": [
                          {
                            "attributes": {
                              "argumentTypes": null,
                              "overloadedDeclarations": [
                                null
                              ],
                              "referencedDeclaration": 40,
                              "type": "uint256",
                              "value": "storedData"
                            },
                            "id": 45,
                            "name": "Identifier",
                            "src": "147:10:1"
                          },
                          {
                            "attributes": {
                              "argumentTypes": null,
                              "overloadedDeclarations": [
                                null
                              ],
                              "referencedDeclaration": 42,
                              "type": "uint256",
                              "value": "x"
                            },
                            "id": 46,
                            "name": "Identifier",
                            "src": "160:1:1"
                          }
                        ],
                        "id": 47,
                        "name": "Assignment",
                        "src": "147:14:1"
                      }
                    ],
                    "id": 48,
                    "name": "ExpressionStatement",
                    "src": "147:14:1"
                  }
                ],
                "id": 49,
                "name": "Block",
                "src": "141:25:1"
              }
            ],
            "id": 50,
            "name": "FunctionDefinition",
            "src": "113:53:1"
          },
          {
            "attributes": {
              "documentation": null,
              "implemented": true,
              "isConstructor": false,
              "kind": "function",
              "modifiers": [
                null
              ],
              "name": "get",
              "scope": 59,
              "stateMutability": "view",
              "superFunction": null,
              "visibility": "public"
            },
            "children": [
              {
                "attributes": {
                  "parameters": [
                    null
                  ]
                },
                "children": [],
                "id": 51,
                "name": "ParameterList",
                "src": "182:2:1"
              },
              {
                "children": [
                  {
                    "attributes": {
                      "constant": false,
                      "name": "",
                      "scope": 58,
                      "stateVariable": false,
                      "storageLocation": "default",
                      "type": "uint256",
                      "value": null,
                      "visibility": "internal"
                    },
                    "children": [
                      {
                        "attributes": {
                          "name": "uint",
                          "type": "uint256"
                        },
                        "id": 52,
                        "name": "ElementaryTypeName",
                        "src": "206:4:1"
                      }
                    ],
                    "id": 53,
                    "name": "VariableDeclaration",
                    "src": "206:4:1"
                  }
                ],
                "id": 54,
                "name": "ParameterList",
                "src": "205:6:1"
              },
              {
                "children": [
                  {
                    "attributes": {
                      "functionReturnParameters": 54
                    },
                    "children": [
                      {
                        "attributes": {
                          "argumentTypes": null,
                          "overloadedDeclarations": [
                            null
                          ],
                          "referencedDeclaration": 40,
                          "type": "uint256",
                          "value": "storedData"
                        },
                        "id": 55,
                        "name": "Identifier",
                        "src": "225:10:1"
                      }
                    ],
                    "id": 56,
                    "name": "Return",
                    "src": "218:17:1"
                  }
                ],
                "id": 57,
                "name": "Block",
                "src": "212:28:1"
              }
            ],
            "id": 58,
            "name": "FunctionDefinition",
            "src": "170:70:1"
          }
        ],
        "id": 59,
        "name": "ContractDefinition",
        "src": "66:176:1"
      }
    ],
    "id": 60,
    "name": "SourceUnit",
    "src": "32:211:1"
  },
  "compiler": {
    "name": "solc",
    "version": "0.5.16+commit.9c3226ce.Emscripten.clang"
  },
  "networks": {
    "5777": {
      "events": {},
      "links": {},
      "address": "0xc21DB75B9B17Cb43Cd983A16BaA6c73b314B1E8f",
      "transactionHash": "0xada7b561df968c3d23485d25cbd22a66d1d4b76a86137dbf1bef858e816ff0c2"
    }
  },
  "schemaVersion": "3.4.3",
  "updatedAt": "2021-10-29T10:14:41.665Z",
  "networkType": "ethereum",
  "devdoc": {
    "methods": {}
  },
  "userdoc": {
    "methods": {}
  }
}

The massive code block above contains the ABI, bytecode, source code, and a ton of other information. From the ABI we can get the list of callable functions. This file contains all the information like network ID, contract address, transaction hash, compiler details, chain details, etc.

You might be wondering why this file is in the UI directory? That’s because Web3.js uses it for its operations. This file enables Web3.js to recognizes the smart contract on the chain. Also, the ABI helps in getting the list of functions.

  • contracts is the directory that holds the Solidity files. You will put all the source code here
  • migrations are the files that inform Truffle which Solidity files need to be migrated to the chain
  • test is for creating test files
  • truffle-config.js contains some configuration settings for Truffle.

Here is the content of truffle-config.js:

const path = require("path");

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
  contracts_build_directory: path.join(__dirname, "client/src/contracts"),
  networks: {
    develop: {
      port: 8545
    }
  }
};

As you can see, client/src/contracts is set to hold compiled code. The development network is set at port 8545. This is the port where this box is running Ganache.

If you look at the top where we installed Ganache, you can see that it was running at 7545 port, but this one is running at 8545 because 7545 is already in use by our installed Ganache. If you wish, you can change this port to 7545 and Truffle will use the Ganache and the accounts we installed instead of the one provided by box. I am keeping it at 8545.

Writing smart contracts in Solidity

Now it’s time to write some code. We will do CRUD operations and manage a list of fruits.

Basically, our application will show a list of different fruits. You can add, update, and delete the fruits.

If you’ve developed apps before, we’ll follow a procedure you’re surely familiar with:

  1. Create an array to hold the names of fruits
  2. Create a function to push a new value to the array
  3. Create a function to change the value at given index
  4. Create a function to delete a value
  5. Create a function to return the array

Now let’s write the code in Solidity.

Create a new file in contracts directory and call it Fruits.sol. We’ll start the file by indicating the license and solidity version we are supporting:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

Next, declare the contract scope where we are going to write all the code:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {

}

Create an array to hold the fruits:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {
  string[] myFruits;
}

This is a string array with a private modifier, which means it can’t be accessed outside the contract and thus we can’t change the value directly.

Next, create a function to add a new value:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {
  string[] myFruits;

  function addFruit(string memory fruitName) public {
      myFruits.push(fruitName);
  }
}

We created a function called addFruit, which accepts a string as a parameter, fruitName. This is declared public so it can be called by the UI or terminal. In the function body, we are simply pushing the value to the array.

Update the value:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {
  string[] myFruits;

  function addFruit(string memory fruitName) public {
      myFruits.push(fruitName);
  }

  function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) {
      if(myFruits.length > fruitIndex){
          myFruits[fruitIndex] = newFruitName;
          return true;
      }
      return false;
  }
}

updateFruit accepts two arguments, fruitIndex and newFruitName, and returns a boolean value. It works like this: if the index is out of the bounds of the array, it returns false. Otherwise, it changes the value of the array with a new provided fruit name at the provided index and return true.

The next steps is to create the delete function:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {
  string[] myFruits;

  function addFruit(string memory fruitName) public {
      myFruits.push(fruitName);
  }

  function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) {
      if(myFruits.length > fruitIndex){
          myFruits[fruitIndex] = newFruitName;
          return true;
      }
      return false;
  }

  function deleteFruit(uint fruitIndex) public returns (bool) {
      if(myFruits.length > fruitIndex){
          for(uint i=fruitIndex; i < myFruits.length-1; i++){
              myFruits[i] = myFruits[i+1];
          }

          myFruits.pop();

          return true;
      }
      return false;
  }
}

Here we are checking the index out of bounds condition and then updating the array by replacing the value with the next value from the provided index. This way, the value at the provided index will be lost. In the end, we pop out the last value and return true.

The last step is to return the array. To read all the values of the array:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract Fruits {
  string[] myFruits;

  function addFruit(string memory fruitName) public {
      myFruits.push(fruitName);
  }

  function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) {
      if(myFruits.length > fruitIndex){
          myFruits[fruitIndex] = newFruitName;
          return true;
      }
      return false;
  }

  function deleteFruit(uint fruitIndex) public returns (bool) {
      if(myFruits.length > fruitIndex){
          for(uint i=fruitIndex; i < myFruits.length-1; i++){
              myFruits[i] = myFruits[i+1];
          }

          myFruits.pop();

          return true;
      }
      return false;
  }

  function getFruits() public view returns (string[] memory) {
      return myFruits;
  }
}

Compiling smart contracts using Truffle

Now that we’ve finished coding our smart contract, it’s time to compile it using Truffle. But first we need to create a migration file to indicate to Truffle that we want to migrate this to the chain.

If you check in the migration folder, you’ll find two JavaScript files:

Javascript Files

They each start with a number, so our third file will start with 3, and so on. The code is nearly standard. For 2_deploy_contracts it is:

var SimpleStorage = artifacts.require("./SimpleStorage.sol");

module.exports = function(deployer) {
  deployer.deploy(SimpleStorage);
};

Let’s add our migration file, 3_fruits_contracts:

var FruitsList = artifacts.require("./Fruits.sol");

module.exports = function(deployer) {
  deployer.deploy(FruitsList);
};

We’re ready to compile and migrate our fruits contract. Move to the terminal and run the following:

> truffle develop

Truffle Console

This command will start the truffle console. It will also show some information such as the chain network, accounts, Mnemonic, etc.

Ganache provides 10 accounts by default. They will be different for you. Please don’t use any of these private keys on the live chain because they are visible to all the visitors of this article, meaning anybody can access these accounts.

Now compile the contracts using this command:

> compile

Compile

You can check in client/src/contracts whether a Fruits.json file is created or not.

We can migrate the compiled file to the chain using this command:

> migrate

This will migrate all three smart contracts to the chain.

Starting Migration
Deploy Contracts
Fruits Contracts

Summary

Finally, our application is on the Ethereum chain. You can see that we spent 0.00153 Ethers in gas fees and the transactions happened from the first account. By default, it always takes the first account. We can perform various operations now.

Getting a list of fruits

We can use Web3.js to react and write various values. Let’s first store the instance of our contract in a variable:

let instance = await Fruits.deployed()

We are using await because everything in blockchain is asynchronous and return a promise.

Now use this instance to get the array:

> let fruits = instance.getFruits()
undefined
> fruits
[]

It will return an empty array because, currently, our fruits array has no value.

Getfruits

Adding a fruit to the list

Let’s add some fruits:

> let result = await instance.addFruit("Apple")
undefined

The result will hold the transaction. Remember, all the read operations are free but any operation that leads to a change in blockchain is subject to a gas fee. This operation is adding a value to the array and hence changing the data. It is, therefore, recorded as a transaction.

Let Result

Now we can again read the array to check the content:

Await Instance

Let’s add a few more fruits to the list:

> await instance.addFruit("Mango")
> await instance.addFruit("Banana")
> await instance.addFruit("Orange")
> await instance.addFruit("Guava")
> await instance.addFruit("Pineapple")
> await instance.addFruit("Water Melon")
> await instance.addFruit("Papaya")
> await instance.addFruit("Strawberry")

Remember, all these operations will cost you Ether. You can save the fee by creating a function in your contract for accepting multiple fruit values at a time.

Read the array now:

Truffle Develop Fruits

Updating a fruit name

You can see in the above image that I misspelled “Guava” as “Guavva.” Let’s correct it using the updateFruit function. It will accept the index and new value. The index is 4.

> await instance.updateFruit(4, "Guava")

Let’s read the array now:

Spelling Fixed

The spelling is successfully fixed.

Deleting a fruit name

The last operation is to delete a value:

> await instance.deleteFruit(4)

Read the values:

Guava Item Gone

As you can see, the “Guava” item is gone from the list.

Conclusion

Creating smart contracts and deploying on the blockchain is fun and powerful. It gives a new perspective from traditional programming. You can create all sorts of applications, such as online voting, digital bank, wallets, auctions etc., using these techniques.

In this tutorial, we demonstrated how to create a smart contract and deploy it on local chain. Now you can try deploying on test networks of Ethereum.

You can improve the current app by introducing more functionality, such as adding multiple fruits at a time to save transaction costs. You might also also put a check to only allow unique fruits. Instead of an array, try to use mappings.

Happy coding!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Akash Mittal I am a software engineer and a die-hard animal lover. My life's goal is to create a mini jungle with a dispensary for stray animals who get diseased or injured.

Leave a Reply