💻Setup a local environment

It's recommended to get familiar with the basic tooling and technologies that are used by Solidity developers before diving into the challenges.

Tip Some instructions are unclear? it's probably intentional - deciphering technical instructions is a major skill needed by professionals. Still struggling? we'd be happy to support in our Discord.

Install Github & VSCode

Install VSCode from here.

Install git from here or from your preferred package manager.

If it's your first time with these technologies, try spending the rest of the day just exploring them and watching YouTube videos or such.

Install Hardhat

Hardhat is our preferred ethereum development environment to use. If you are already used to another environment, it wouldn't hurt learning a new techonology. It's quite simple and supports multiple plugins. ( See example hardhat-foundry ). Install Hardhat globally (from your terminal, anywhere):

npm i -g hardhat --save-dev
npm i -g hardhat-shorthand

Run hardhat and quit immediately, just to ensure it is installed properly.

Play with Hardhat

Create a new folder with an Hardhat project by running hardhat init

Follow the options/questions based on your intuition, if something doesn't seem right - retry.

You should end up with a folder structure similar to this:

.
├── artifacts // Compiled contract bytecode and metadata
├── cache // Ignore
├── contracts // The source code of the contracts
├── hardhat.config.js // Hardhat configuration
├── node_modules // Node.js packages
├── package-lock.json // Packages index
├── package.json // Packages index
├── scripts // Developer scripts or snippets
└── test // Unit tests, and easy interaction with the contract

Our focus at the start should be the contracts, and the test folders.

contracts - where the source code is, the actual contract and logic, and where auditors mostly work their magic.

test - JavaScript unit tests. You might wonder now - why JavaScript? how is this code related to the code deployed to the blockchain? etc.

Don't worry - it is only used for local testing during the smart contract developement phase. Hardhat provides an abstraction layer which makes our lives much easier, by giving us many tools to interact with a fake local ethereum network embedded inside of it. Hardhat also abstracts `ethers.js`, and others, to give us convenient JavaScript interaction capabilities with our fake local network.

So for example:

const LockContract = await ethers.getContractFactory("Lock")

getContractFactory takes the Lock.sol contract's name as a parameter and returns a factory object that can be used to deploy or interact with instances of the contract.

We will now try a more complicated example, in which we will:

  • We deploy a simple token contract (SimpleToken) with an initial supply.

  • We transfer some amount of tokens from the deployer to user1.

  • Finally, we check user1's balance to ensure the transfer was successful.

Follow the comments to ensure you understand.

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

describe('Basic Token Interaction', function () {
    let deployer, user1, user2;
    let token;

    const INITIAL_SUPPLY = 1000000n * 10n ** 18n;
    const TRANSFER_AMOUNT = 100n * 10n ** 18n;

    before(async function () {
        // Setup scenario
        [deployer, user1, user2] = await ethers.getSigners();

        // Deploy a simple token contract
        const TokenContract = await ethers.getContractFactory('SimpleToken', deployer);
        token = await TokenContract.deploy(INITIAL_SUPPLY);

        // Check the total supply of the token
        expect(await token.totalSupply()).to.eq(INITIAL_SUPPLY);
    });

    it('Token Transfer', async function () {
        // Transfer tokens from deployer to user1
        await token.connect(deployer).transfer(user1.address, TRANSFER_AMOUNT);
        
        // Check user1's token balance
        expect(await token.balanceOf(user1.address)).to.eq(TRANSFER_AMOUNT);
    });

    it('Check Balance', async function () {
        // User1 checks their balance
        const balance = await token.balanceOf(user1.address);
        
        // Expect balance to be equal to the transferred amount
        expect(balance).to.eq(TRANSFER_AMOUNT);
    });
});

After we've gotten a grasp for what hardhat is, we can go through a few more commands:

Compiling the contract

This one is quite simple, try to run hh compile and inspect what happened.

Hardhat Tasks

To get a grasp of what hardhat tasks are, try pasting this task into hardhat.config.js:

task("accounts", "Prints the list of accounts", async () => {
  const accounts = await ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address)
  }
})

Then run hh acounts - you know you're good when you see a bunch of hex addresses.

Hardhat tests

hh test - try to figure out what it did

Then navigate to the main contract code (e.g. contracts/Lock.sol ) and utilize hardhat's logging "hack":

  • Add import "hardhat/console.sol" to the contract, if not already present

  • Find a decent place to add console.log("hi")

  • hh test

  • Try to see your logged string

Although simple, this is a powerful utility which will help us down the line a lot.

Fork Mainnet

This is a powerful feature, instructing hardhat to use mainnet contracts on our fake hardhat blockchain - allowing us to benefit real data in our fake environment.

  • Get free API key from https://app.infura.io

  • In the dashboard, where the API key is, copy the ethereum network url and paste as follows in hardhat.config.js:

module.exports = {
  solidity: "0.8.19", 
  networks: {
    hardhat: {
      forking: {
        url: "https://mainnet.infura.io/v3/<YOUR-kEY>"
      }
    }
  }
};
  • Then run hh test

You should see a warning about running from a fork to indicate success.

Debugging Hardhat tests in VSCode

We've already demonstrated above how to make use of the hardhat/console.sol functionality to increase understanding of contracts during runtime.

Since the hardhat unit tests are just JavaScript, they are very trivial to debug.

Here's a typical vscode launch.json configuration, in yarn + hardhat based projects:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Yarn Hardhat Debugging",
            "console": "integratedTerminal",
            "runtimeExecutable": "yarn",
            "runtimeArgs": [
                "hardhat",
                "test",
                "${workspaceFolder}/test/SomeTest.js"
            ]
        }
    ]
}

Pressing the debug button on a test JavaScript file should prompt you to manually enter a launch.json configuration. You will then have to customize it once to your own project, and debugging the tests would be a breeze!

Last updated