Web3

Unit testing and deploying smart contracts with Forge

In December 2021, the world’s largest crypto-native funding agency, Paradigm Lab’s CTO Georgios launched a weblog concerning the discharge of a brand new framework for (evm-based) smart contract growth, referred to as Foundry.

It took the Crypto group by storm, and quickly grew to become the trade commonplace for growth and testing of smart contracts, owing a lot to its effectivity compared to different frameworks.

So as to perceive the importance of Foundry, we have to first look into the issues it tries to unravel.

The principle downside that lies with frameworks like Hardhat and Truffle is that they require the builders to know a scripting language like JavaScript/TypeScript with the intention to work with the framework.

As these scripting languages are internet development-heavy, the solidity developer needn’t know such languages for the smart contract growth as it’s thought-about extra backend oriented.

One other difficulty is that hardhat itself is carried out utilizing TypeScript, so it’s slower than Foundry because the latter is carried out utilizing Rust.

(Word: In case you are serious about checking the benchmarks, please take a look at this simulation)

Graph comparing compilation times between Forge and Hardhat

Foundry has plenty of cool options except for this like:

  • Name stack traces
  • Interactive debugger
  • Inbuilt-fuzzing
  • Solidity scripting

Now, I hope you will have an summary of Foundry and the need of testing smart contracts utilizing Solidity. Foundry ships with two superb CLI instruments out-of-the-box:

  • Forge: Used for testing and deployment of smart contracts
  • Solid: Used to work together with deployed smart contracts

On this article we’re going to cowl the next:

Let’s get began.

Putting in Foundry

Putting in Foundry is easy and easy.

Open up your terminal and run:

curl -L  | bash && foundryup

As soon as Foundry is put in, you can begin utilizing Forge and Solid straightaway.

For some OS, you may wish to install rust earlier than putting in Foundry.

Organising a Foundry venture

You possibly can immediately setup a Foundry venture by instantly by working

forge init <PROJECT_NAME>

To make your life simpler, I’ve created a template repository, with which you may get began extra simply. It incorporates the required libraries, scripts and listing setup. So, all you must do is simply run the next command in your terminal:

The above command creates a brand new listing referred to as foundry-faucet and initializes a brand new Foundry venture utilizing my template. This may be the listing construction. The vital directories and recordsdata that we wish to concentrate on are:

Directory structureDirectory structure

  • lib: This incorporates all of the dependencies/libraries that we’re going to use. For instance, if we wanna use Solmate, it should reside as a git submodule inside this folder
  • scripts: This folder has all of the scripts, like deploying and verifying contracts
  • src: This folder has all of the contracts and the assessments related with the contracts
  • foundry.toml: This file incorporates the configuration choices for the present Foundry venture

We also needs to replace and set up the libraries used; for that run the next instructions:

git submodule replace --init --recursive
forge set up

Making a easy Faucet contract

Now, we’re going to implement a faucet contract for our ERC20 token which may drip tokens when requested. We will additionally prohibit the quantity of tokens per request by setting a restrict which will likely be 100 by default in our contract.

Open up the src/Faucet.sol file and add the next code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;

import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";

contract Faucet is Ownable {
   /// Tackle of the token that this faucet drips
   IERC20 public token;

   /// For price limiting
   mapping(handle => uint256) public nextRequestAt;
   /// Max token restrict per request
   uint256 public restrict = 100;

   /// @param _token The handle of the tap's token
   constructor(IERC20 _token) {
       token = _token;
   }

   /// Used to ship the tokens
   /// @param _recipient The handle of the tokens recipient
   /// @param _amount The quantity of tokens required from the tap
   perform drip(handle _recipient, uint256 _amount) exterior {
       require(_recipient != handle(0), "INVALID_RECIPIENT");

       require(_amount <= restrict, "EXCEEDS_LIMIT");

       require(nextRequestAt[_recipient] <= block.timestamp, "TRY_LATER");
       nextRequestAt[_recipient] = block.timestamp + (5 minutes);

       token.switch(_recipient, _amount);
   }

   /// Used to set the max restrict per request
   /// @dev This methodology is restricted and must be referred to as solely by the proprietor
   /// @param _limit The brand new restrict for the tokens per request
   perform setLimit(uint256 _limit) exterior onlyOwner {
       restrict = _limit;
   }
}

Our faucet contract has been added. Now we are able to go forward and compile the contracts by working:

forge construct

If every little thing goes effectively, it is best to see an analogous output:

[⠒] Compiling...
[⠒] Compiling 14 recordsdata with 0.8.13
Compiler run profitable

Candy! We’ve efficiently arrange our Foundry venture and compiled our contract with none errors! Good job, anon 🎉

Now, we are able to go forward and begin testing our Faucet contract.

Unit testing utilizing Forge

As you recognize, not like Hardhat, Forge helps us write unit assessments utilizing Solidity.

Should you open the src/take a look at/Faucet.t.sol file you’ll already see some imports of utils and a BaseSetup contract.

Base setup contractBase setup contract

It has some preliminary setup that initializes a couple of variables that we are able to use in our assessments. As well as, the setUp() perform is just like beforeEach in hardhat and it runs earlier than each take a look at.

The setUp() perform creates two addresses and labels them Alice and Bob. It’s useful whenever you attempt to debug through name traces because the label seems within the traces alongside with the handle.

(Word: vm.label known as a cheatcode and it’s particular to Forge; It helps us to do some particular operations by interacting with the digital machine within the take a look at env. We’ll be seeing extra cheatcodes in the course of the course of the article. For the total checklist of cheatcodes, you may refer to this link)

Change the Faucet.t.sol with the next code to get began with the unit assessments;


Extra nice articles from LogRocket:


// SPDX-License-Identifier: MIT
pragma solidity >=0.8;

import {console} from "forge-std/console.sol";
import {stdStorage, StdStorage, Take a look at} from "forge-std/Test.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";

import {Utils} from "./utils/Utils.sol";
import {Faucet} from "../Faucet.sol";
import {MockERC20} from "../MockERC20.sol";

contract BaseSetup is Take a look at {
   Utils inner utils;
   Faucet inner faucet;
   MockERC20 inner token;

   handle payable[] inner customers;
   handle inner proprietor;
   handle inner dev;
   uint256 inner faucetBal = 1000;

   perform setUp() public digital {
       utils = new Utils();
       customers = utils.createUsers(2);
       proprietor = customers[0];
       vm.label(proprietor, "Owner");
       dev = customers[1];
       vm.label(dev, "Developer");

       token = new MockERC20();
       faucet = new Faucet(IERC20(token));
       token.mint(handle(faucet), faucetBal);
   }
}

You possibly can see that we have now now created new state variables like faucet, token and additionally we’ve renamed alice and bob to proprietor and dev for straightforward interpretation. On this context, dev is somebody who requests tokens from the tap whereas the proprietor is the proprietor of the tap itself.

Within the final three traces of the setUp() methodology, we deploy a mock token for the tap, cross its handle within the constructor of the new Faucet() (faucet deployment), and then name and mint some tokens to the deployed faucet contract.

Now, we’ll inherit the BaseSetup contract to jot down unit assessments for our Faucet contract.

Under the BaseSetup contract, add the next code:

contract FaucetTest is BaseSetup {
   uint256 amountToDrip = 1;

   perform setUp() public override {
       tremendous.setUp();
   }

As talked about earlier, the setUp() methodology runs earlier than all of the testcases and right here we’re calling the setUp() methodology of the bottom contract which is the BaseSetup contract through tremendous.setUp().

Alright, now allow us to begin including unit assessments for our contract. Proper beneath the setUp() methodology of the FaucetTest contract, add the next piece of code:

   perform test_drip_transferToDev() public {
       console.log(
           "Should transfer tokens to recipient when `drip()` is called"
       );
       uint256 _inititalDevBal = token.balanceOf(dev);

       /// Make it possible for preliminary dev steadiness is Zero
       assertEq(_inititalDevBal, 0);

       /// Request some tokens to the dev pockets from the pockets
       faucet.drip(dev, amountToDrip);

       uint256 _devBalAfterDrip = token.balanceOf(dev);

      /// The distinction must be equal to the quantity requested from the tap
       assertEq(_devBalAfterDrip - _inititalDevBal, amountToDrip);
   }

The above code helps us to check the drip() methodology. The workflow is easy.

  1. First, retailer the preliminary steadiness of the dev in a variable (_inititalDevBal)
  2. Ensure it’s 0, as we didn’t mint any tokens to the dev. That is what the road assertEq(_inititalDevBal, 0); does
  3. Then name the drip() methodology from the faucet contract occasion
  4. Fetch the steadiness of dev after the drip() known as
  5. The distinction between the steadiness of the dev account earlier than and after the drip() must be equal to amountToDrip, which is saved as a state variable within the FaucetTest contract

Now, allow us to save the file and run the take a look at: forge take a look at.

It is best to see the output in your terminal one thing just like this:

Compiling test results in terminalCompiling test results in terminal

Cool! Let’s add some extra assessments.

The above take a look at verifies that the drip() methodology transfers the tokens to the dev. So, we also needs to examine that the switch is a sound one, which suggests the token steadiness of the tap must be decreased.

Add the next take a look at beneath — the test_drip_transferToDev() methodology.

 perform test_drip_reduceFaucetBalance() public {
       console.log("The faucet balance should be reduced");
       faucet.drip(dev, amountToDrip);
       assertEq(token.balanceOf(handle(faucet)), faucetBal - amountToDrip);
   }

This makes certain that the tokens that the dev acquired are literally despatched from the tap — if that’s the case, the steadiness of the tap must be decreased.

We will be certain that by working the take a look at suite once more : forge take a look at

If every little thing goes effectively, then your output must be just like this:

Compiling test results in terminalCompiling test results in terminal

Candy! In case you have seen, we have now console.log statements in our take a look at circumstances, however they don’t seem to be exhibiting up within the console. The reason being that Forge doesn’t show logs by default. To get the logs displayed, we have to run the command with verbosity 2 : forge take a look at -vv will show the logs.

Compiling test results in terminalCompiling test results in terminal

Additionally if there are any occasions which can be emitted by your contract, you may view them within the assessments with verbosity three (-vvv). You may get an in depth name hint in your assessments as excessive as verbosity stage 5, which helps in higher debugging.

Alright, let’s preserve including extra assessments. Now we’re going to take a look at our price restrict mechanism. There must be no less than a five-minute interval earlier than calling drip() with the identical recipient handle.

   perform test_drip_revertIfThrottled() public {
       console.log("Should revert if tried to throttle");
       faucet.drip(dev, amountToDrip);

       vm.expectRevert(abi.encodePacked("TRY_LATER"));
       faucet.drip(dev, amountToDrip);
   }

vm.expectRevert(bytes32) is one other cheat code that checks if the following name reverts with the given error message. On this case, the error message is TRY_LATER. It accepts the error message as bytes not as a string, therefore we’re utilizing abi.encodePacked.

Should you bear in mind, I discussed that Forge ships with a fuzzer out-the-box. Let’s give it a attempt.

We mix the assessments test_drip_transferToDev and test_drip_reduceFaucetBalance, and as an alternative of passing the inputs manually, we might enable the fuzzer to enter the values in order that we are able to make it possible for our contract handles totally different inputs.

   perform test_drip_withFuzzing(handle _recipient, uint256 _amount) public {
       console.log("Should handle fuzzing");
       /// inform the constraints to the fuzzer, in order that the assessments do not revert on unhealthy inputs.
       vm.assume(_amount <= 100);
       vm.assume(_recipient != handle(0));
       uint256 _inititalBal = token.balanceOf(_recipient);
       faucet.drip(_recipient, _amount);
       uint256 _balAfterDrip = token.balanceOf(_recipient);
       assertEq(_balAfterDrip - _inititalBal, _amount);
       assertEq(token.balanceOf(handle(faucet)), faucetBal - _amount);
   }

Fuzzing is property-based testing. Forge will apply fuzzing to any take a look at that takes no less than one parameter.

Whenever you execute the take a look at suite, you will discover the next line within the output:

[PASS] test_drip_withFuzzing(handle,uint256) (runs: 256)

From the above output we are able to infer that the Forge fuzzer referred to as the test_drip_withFuzzing() methodology 256 instances with random inputs. Nonetheless, we are able to override this quantity utilizing the FOUNDRY_FUZZ_RUNS setting variable.

Now, allow us to add a pair extra assessments for the owner-only methodology setLimit()

perform test_setLimit() public {
       console.log("Should set the limit when called by the owner");
       faucet.setLimit(1000);

       /// the restrict must be up to date assertEq(faucet.restrict(), 1000); } perform test_setLimit_revertIfNotOwner() public { console.log("Should revert if not called by Owner"); /// Units the msg.sender as dev for the following tx vm.prank(dev); vm.expectRevert(abi.encodePacked("Ownable: caller is not the owner")); faucet.setLimit(1000); }

Within the test_setLimit_revertIfNotOwner() methodology, a brand new cheatcode vm.prank(handle) is used. It pranks the vm by overriding the msg.sender with the given handle; in our case it’s dev. So, the setLimit() ought to revert with the caller isn't the proprietor message as our Faucet contract inherits the Ownable contract.

Okay allow us to make it possible for no assessments fail by working forge take a look at once more.

Forge test terminal outputForge test terminal output

Candy 🥳 Now it’s time for deployment.

Contract deployment to Kovan testnet

Create a brand new file from .env.instance file and title it as .env. Please fill your INFURA_API_KEY and the PRIVATE_KEY (with Kovan testnet funds).

As soon as all of the fields are populated, you’re all set for deployment to Kovan. Earlier than deploying the tap, we have to deploy our ERC20 token.

Yow will discover the deployment scripts contained in the scripts listing, and deploy the MockERC20 token to Kovan testnet by executing the ./scripts/deploy_token_kovan.sh bash script.

The output would look one thing like this:

Deployer: (YOUR_DEPLOYMENT_ADDRESS)

Deployed to: 0x1a70d8a2a02c9cf0faa5551304ba770b5496ed80

Transaction hash: 0xa3780d2e3e1d1f9346035144f3c2d62f31918b613a370f416a4fb1a6c2eadc77

To make it possible for the transaction truly went by, you may search the transaction hash in

Copy the Deployed to: handle, as it’s the handle of the MockERC20 token that we must always use for deploying our Faucet contract. To deploy the tap, you may execute the ./scripts/deploy_faucet_kovan.shscript.

It can immediate you to enter the token handle; then enter the copied MockERC20 token handle that was deployed earlier.

The output ought to look one thing like this:

Entering token contract address and compilingEntering token contract address and compiling

Woohoo 🚀🚀 We’ve efficiently compiled, examined, and deployed our contract to the Kovan testnet utilizing Forge

We nonetheless have to confirm the contract on Etherscan and additionally mint some MockERC20 tokens to the Faucet (you should use forged for this!) for it to work as meant. I’ll go away this to you guys as an train to attempt it yourselves!

As at all times, you will discover the GitHub repository for this text here.

Conclusion

On this article we solely coated a couple of items of Forge. Foundry is a really highly effective framework for smart contracts and it’s quickly growing as effectively.

There are extra cool options like code-coverage, contract verification, fuel snapshots, name traces, and interactive debugging. Be at liberty to mess around with the repo by testing out extra options. Pleased coding 🎊

Be a part of organizations like Bitso and Coinsquare who use LogRocket to proactively monitor their Web3 apps

Consumer-side points that affect customers’ skill to activate and transact in your apps can drastically have an effect on your backside line. Should you’re serious about monitoring UX points, mechanically surfacing JavaScript errors, and monitoring gradual community requests and element load time, try LogRocket.LogRocket Dashboard Free Trial BannerLogRocket Dashboard Free Trial Bannerhttps://logrocket.com/signup/

LogRocket is sort of a DVR for internet and cellular apps, recording every little thing that occurs in your internet app or web site. As an alternative of guessing why issues occur, you may combination and report on key frontend efficiency metrics, replay consumer classes alongside with software state, log community requests, and mechanically floor all errors.

Modernize the way you debug internet and cellular apps — Start monitoring for free.

DailyBlockchain.News Admin

Our Mission is to bridge the knowledge gap and foster an informed blockchain community by presenting clear, concise, and reliable information every single day. Join us on this exciting journey into the future of finance, technology, and beyond. Whether you’re a blockchain novice or an enthusiast, DailyBlockchain.news is here for you.
Back to top button