Setting up the best smart contract development environment

Adarsh_Moradiya
Simform Engineering
11 min readApr 7, 2023

In the rapidly evolving landscape of blockchain development, smart contracts serve as the fundamental building blocks for creating decentralized applications. Therefore, it is crucial for developers to create a perfect development environment that encourages the construction of reliable and effective smart contracts.

In this blog post, we’ll guide you through the process of creating an optimal environment for smart contract development, ensuring that you have all the necessary tools and frameworks to maximize your productivity and efficiency.

Why is a good development environment important?

A good development environment is necessary for creating smart contracts to ensure productivity, accuracy, security, cooperation, and compatibility. An effective development environment can help programmers write, test, and debug their code more quickly and cost-effectively. Additionally, it can assist in ensuring that the code is precise and error-free prior to deployment, avoiding costly errors.

Security is a major concern for smart contracts since they frequently handle private information and money. Since many developers frequently collaborate on the same project when developing smart contracts, a strong development environment may make collaboration easier by offering tools for version control, code review, and communication.

Prerequisites

  • NodeJS
  • VS code
  • Little patience

What is Hardhat?

Hardhat is a development environment for building and testing Ethereum smart contracts. It is an open-source framework that allows developers to compile, deploy, test, and debug smart contracts using a command-line interface.

Hardhat supports multiple networks, including local, test, and mainnet, and provides a comprehensive suite of built-in plugins and tools for smart contract development. Some of its features include contract mocking, gas reporting, and debugging with stack traces. Hardhat is becoming increasingly popular among Ethereum developers due to its flexibility, speed, and ease of use.

There are several alternatives to Hardhat in the market, each with its own set of features and benefits. Some popular alternatives to Hardhat include:

  1. Truffle: Truffle is a popular development framework for Ethereum smart contracts that provides a suite of tools for building, testing, and deploying smart contracts. It supports several Ethereum networks and has a large community of developers.
  2. Brownie: Brownie is a Python-based smart contract development framework that allows developers to write, test, and deploy smart contracts quickly and efficiently. It provides built-in support for popular Ethereum libraries and tools.
  3. Remix: Remix is a web-based IDE for developing smart contracts on the Ethereum blockchain. It provides a user-friendly interface for writing and testing smart contracts and supports several Ethereum networks.
  4. Embark: Embark is a decentralized application development framework that provides a set of tools for building and deploying smart contracts. It supports several Ethereum networks and provides a comprehensive suite of features for smart contract development.
  5. Buidler: Buidler is an Ethereum development environment that allows developers to write, test, and deploy smart contracts quickly and easily. It provides a command-line interface for compiling and deploying smart contracts and supports several Ethereum networks.

Ultimately, the choice of which smart contract development framework to use depends on individual preferences and project requirements. We chose Hardhat for our project because it offers a fast and efficient development environment with built-in tools and plugins for smart contract development. Its comprehensive suite of functionalities, including contract testing, debugging, and gas reporting, simplifies the development process and ensures secure and efficient smart contracts

So, let’s create a new empty repository and open it in VS Code. Now open the terminal in vs code and install Hardhat, Chai (for testing), and other useful tools by running the below command.

$ npm init -y
$ npm install --save hardhat @nomicfoundation/hardhat-toolbox chai dotenv hardhat-gas-reporter

Now create a contracts folder and inside this folder, create a new solidity contract file,IncrementDecrement.sol and add the following smart contract code:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract IncrementDecrement{
int256 public counter;

function increment() public {
counter++;
}

function decrement() public {
counter--;
}

function reset() public {
counter = 0;
}
}

The above-mentioned smart contract is a simple counter with increment, decrement, and reset functionality. and we will use a compiler version greater than 0.8.17 to compile this contract.

If you’re new to writing smart contracts, don’t worry! We’ve got you covered with a helpful tutorial that will guide you through the process. Check out the link below to get started with smart contracts and learn how to create your own!

Tip: solidity functions should be designed with a gas limit in mind to prevent gas exhaustion attacks. also check for reentrancy, integer overflow/underflow and unchecked exteranl calls to create secure smart contract.

To compile your smart contract, you’ll need a hardhat config file. Create a new file named hardhat.config.js in the root folder of your project and copy the following code into it:

module.exports = {
solidity: {
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
runs: 1000,
},
},
},
networks: {
hardhat: {
chainId: 1337,
},
},
};

To set up your configuration, you have to export an object from hardhat.config.js. This object can have entries like defaultNetwork, networks, solidity, paths and mocha. Our configuration file contains two main sections: solidity and networks.

In the solidity section, the version of Solidity used for the smart contract is specified as 0.8.17. Verify the smart contract file’s solidity version from the hardhat.config.js file. If it does not match, then it will give an error. The optimizer setting is also enabled and set to run 1000 times, which helps to optimize the bytecode generated by the compiler and reduce gas costs.

In the networks section, the hardhat network is defined with a chainId of 1337. This allows you to test your smart contract on a local development network without having to deploy it to a real blockchain network. You can add additional networks here as needed for testing and deployment purposes.

Next, you’ll need to compile your smart contracts. To do so, run the following command:

$ npx hardhat compile

You will notice that an artifacts folder will be generated if the compilation is successful. This folder typically contains the compiled bytecode and Application Binary Interface (ABI) files of your smart contracts.

To streamline our testing process, we can create a separate file called utils.js to store commonly used functions. One such function we can include isdeployContract(), which can be used to deploy a smart contract multiple times during testing. Smart contracts will be deployed on the hardhat local network when we deploy them using deployContract function during testing.

Also, we’ll make a file called increment-decrement.js to test a certain smart contract’s functioning. We will create test cases in this file to make sure the smart contract is operating properly. By separating the testing logic into its own file, we can keep our code organized and make it easier to maintain and debug in the future.

Both the file utils.js and increment-decrement.js will be created inside the test folder.

code for utils.js:

const { ethers } = require('hardhat');

const deployContract = async (contractName, args) => {
[signer] = await ethers.getSigners();
const Contract = await ethers.getContractFactory(contractName);
const contract = await Contract.connect(signer).deploy(...(args || []));
await contract.deployed();

return contract;
};

module.exports = { deployContract};

code for increment-decrement.js

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { deployContract } = require("./utils");

describe("Testing Increment/Decrement contract", async () => {
let alice;
let aliceAddr;
let contract;

before(async () => {
[alice] = await ethers.getSigners();
aliceAddr = await alice.getAddress();

//deploy contract
contract = await deployContract("IncrementDecrement");
});

it("should increment the counter", async () => {
await contract.increment();
expect(await contract.counter()).to.equal(1);
});
it("should reset the counter", async () => {
expect(await contract.counter()).to.equal(1);
await contract.reset();
expect(await contract.counter()).to.equal(0);
});
it("should decrement the counter", async () => {
await contract.decrement();
expect(await contract.counter()).to.equal(-1);
});
});

In the code snippet above, a test case has been implemented to check the functionality of the smart contract. This test case verifies that the counter’s value is increasing or decreasing correctly when the relevant functions are called.

This is a crucial step in ensuring that the smart contract works as intended and is free from any potential bugs or issues. By testing the contract thoroughly, we can ensure that it behaves as expected and is safe to deploy on a live network.

It is important to note that the test cases presented in this example contract are relatively simple and straightforward. However, in more complex smart contracts, the test cases may be longer and more involved.

To run the test cases, run the below command:

$ npx hardhat test

Tip: you can check your smart contract’s vulnerability by using open source tools like slither, mythrill, securify and many more.

So far, so good. Up to this point, we have created a basic smart contract and written test cases for it. Now we will create tasks for deploying and verifying smart contracts on different networks.

What are tasks? and why is needed?

In Hardhat, tasks are scripts that automate common development and deployment tasks. They provide an easy way to execute a sequence of operations or scripts and can be customized to fit specific needs. Tasks can be used to compile contracts, deploy them, run tests, and perform other operations.

Tasks are essential in Hardhat as they allow developers to automate repetitive tasks, saving time and improving productivity. They can be easily customized to fit the needs of a specific project or workflow and can be shared with other developers. Additionally, tasks can be used to ensure consistency across different environments, ensuring that the same set of tasks is executed in the same way every time.

Hardhat provides a variety of built-in tasks that cover common tasks, such as compiling, testing, and deploying contracts. Developers can also create custom tasks to automate specific workflows or tasks. Overall, tasks are a powerful feature in Hardhat that can significantly improve the development process and streamline tasks. You can see the detailed explanation for creating tasks here.

For the tasks, create a file increment-decrement.js inside task folder. and add the following code to it:

const { task } = require("hardhat/config");
const fs = require("fs");
const path = require("path");
const contractName = "IncrementDecrement";
const NETWORK_MAP = {
1337: "hardhat",
1: "mainnet",
5: "goerli",
137: "polygon",
80001: "mumbai",
};
task("deploy:increment-decrement", "deploy IncrementDecrement", async () => {
const { chainId } = await hre.ethers.provider.getNetwork();
const networkName = NETWORK_MAP[chainId];

console.log("Deploying contract...");

const Contract = await hre.ethers.getContractFactory(contractName);
const contract = await Contract.deploy();
await contract.deployed();
console.log("contract Deployed at ", contract.address);
const info = {
contract: contract.address,
};
const dir = path.join(`${__dirname}/../publish/${networkName}/`);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

fs.writeFileSync(
`${dir}/${contractName}.json`,
JSON.stringify(info, null, 2)
);
});

task("verify:increment-decrement", "verify IncrementDecrement", async () => {
const { chainId } = await hre.ethers.provider.getNetwork();
const networkName = NETWORK_MAP[chainId];
const rawData = fs.readFileSync(
path.join(__dirname, `../publish/${networkName}/${contractName}.json`)
);
const address = JSON.parse(rawData.toString()).contract;

if (!address) {
throw new Error("Contract address not found");
}
console.log("Verifying contract...");
try {
await run("verify:verify", {
address: address,
constructorArguments: [],
});
} catch (e) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already verified!");
} else {
console.log(e);
}
}
});

task("abi:increment-decrement", "Export IncrementDecrement ABI", () => {
try {
const rawData = fs.readFileSync(
path.join(
__dirname,
"../artifacts/contracts",
`${contractName}.sol/${contractName}.json`
)
);
const info = JSON.parse(rawData.toString());
const dir = `${__dirname}/../publish/abis/`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
fs.writeFileSync(
path.join(`${dir}/${contractName}.json`),
JSON.stringify(info.abi, null, 2)
);
} catch (error) {
console.error("Writing ABI error: ", error);
}
});

In the above code, we have defined three tasks to perform different actions related to a smart contract named "increment-decrement."

  1. deploy : increment-decrement: This task deploys the smart contract on a given network using ethers library. It will also store the deployed contract address in publish/network-name/contract-name.json file for future reference.
  2. verify : increment-decrement: This task verifies the deployed contract on the selected network. It will take the contract address from the publish/network-name/contract-name.json file and verify it on a given network.
  3. abi : increment-decrement: This task exports the ABIs (Application Binary Interface) of the smart contract into a specified folder. These ABIs can be used in the future to interact with the smart contract.

Overall, these tasks facilitate the deployment, verification, and interaction with the “increment-decrement” smart contract on the desired network.

To run this task, first, we need to import it into our hardhat.config.js file. Also, if we need to add network specifications on whichever network we need to deploy and verify smart contracts like goerli testnet, Ethereum mainnet, polygon, etc.

For deploying on testnet or mainnet (other than Hardhat), we need an API key. You can get an API key for a specific network from Alchemy or Infura. And to verify, we also need an API key from a relative block explorer like etherscan/polygonscan. Get all these necessary API keys and store them in .env file as shown below:

We also need to update the configuration hardhat.config.js to add other network configurations and account private keys to make transactions.

require("dotenv").config();

require("@nomicfoundation/hardhat-toolbox");
require("hardhat-gas-reporter");
require("./task/increment-decrement");

const ACCOUNT_PRIVATE_KEY = process.env.ACCOUNT_PRIVATE_KEY;

module.exports = {
solidity: {
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
runs: 1000,
},
},
},
networks: {
hardhat: {
chainId: 1337,
},
goerli: {
url: `https://eth-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_GOERLI_API_KEY}`,
accounts: [`${ACCOUNT_PRIVATE_KEY}`] ,
chainId: 5,
gas: "auto",
},
},
etherscan: {
apiKey: {
goerli: process.env.ETHERSCAN_API_KEY,
},
},
gasReporter: {
enabled: true,
},
};

Now we have to import tasks for deploying and verify the smart contracts which we have created into the hardhat.config.js file

In the configuration file, we have currently configured two networks: Hardhat and GoerliTestnet. If you need to add more networks, it’s as simple as adding a new configuration object for the desired network. This way, the network can be easily integrated into the system without any additional coding or setup required.

To add networks other than Hardhat, it’s necessary to obtain an RPC URL. This can be done using a third-party application such as Alchemy or Infura. The URL provided by these services includes an API key, which should be stored securely in the .env file.

We have also added etherscan API key which is needed to verify smart contracts on etherscan.

For better performance and a simple understanding, we will use some plugins that we installed at the beginning. e.g., we have used hardhat-gas-reporter , to get the data about how much gas is used while executing the function. Also, we used@nomicfoundation/ardhat-toolbox to verify the contract directly from our terminal.

To run the task, follow these commands:

  • To deploy smart contract :
$ npx hardhat deploy:increment-decrement 

we can also select the network on which it should be deployed using the — network flag, followed by the network name. By default, Hardhat is set as the primary network.

If you don’t specify any network, the smart contract will be deployed on the local Hardhat network. You also have the option to change the default network by updating the hardhat.config.js file.

$ npx hardhat deploy:increment-decrement --network goerli
  • To verify smart contract :
$ npx hardhat verify: increment -decrement --netowrk goerli
  • To export ABIs to a specified folder:
$ npx hardhat abi:increment-decrement

If the above task has been executed successfully, a publish folder will be generated within your repository. This folder will contain the ABIs and contract addresses of the deployed contract. Therefore, your folder structure should resemble the following:

Great work, your smart contract development is now complete. This basic approach serves as a highly effective foundation for developing smart contracts. You can modify and customize it in any way that suits your specific needs and use case. Keep iterating and refining the contract until you achieve the desired results.

Wrap Up

We have explored the steps involved in setting up a robust and efficient smart contract development environment. We have covered everything from setting up a local blockchain network to writing and deploying a smart contract using popular development frameworks like Hardhat.

With this tutorial, you now have the knowledge and expertise to create powerful, secure, and scalable smart contracts that can be deployed in real-world applications. As you continue to hone your skills and gain more experience, you can further refine and optimize your development environment to meet the specific needs of your projects.

If you find any difficulty or error, you can connect with me through Twitter or LinkedIn.

Happy coding …

Follow Simform Engineering to keep up with the latest trends in the development ecosystem.

--

--