Foundry – Solidity Tests, Fuzzing and so much more!
Tags: web3, foundry, solidity, chainlink
Published: 2022-06-13 16:10:39

To say Foundry is an up and comer in the Solidity framework space would be an understatement. It is new but has a strong community backing and solid development. Moreover, it has now been described with one of the most overused words in the IT industry – “blazingly fast”.

If you are starting out with Foundry, you might have already come across the fact that it is made in Rust and has been adopted from DappTools. But the main hype behind it is that it helps you write your tests in Solidity. 

Until so long, the de facto testing framework was [Chai](https://www.chaijs.com)/[Mocha](https://mochajs.org). That didn’t change as the industry weight started shifting from Truffle to Hardhat. The only drawback one might consider was that it needed a Solidity Developer who also knew Javascript. Granted most already do but the question raised by Foundry is that if you are writing your contracts in Solidity then why not the tests as well.

In this article we will focus on developing contracts with Foundry and testing them (using solidity). Since the focus is not on contract build but on the framework itself, we will use the boring NFT contract we developed in my [Chainlink VRF series](https://dev.to/abhikbanerjee99/series/18388). We will replicate that stuff with Foundry in this article.


> Please note: This article assumes that you already have familiarity with Solidity and that you have already set up Foundry on your system.
 

I am a Windows user (sorry…) so I will be using WSL in my case. The process is same on WSL as well. The thing you might need to know is that when you open the distro you have installed, it provides an option to load all other partition drives into the distro at `/mnt` directory. Since I am using Ubuntu 22.04 LTS distro as my WSL, it automatically does that.

You can follow the awesome articles on how to [install Rust in WSL](https://docs.microsoft.com/en-us/windows/dev-environment/rust/setup) and then install Foundry with the `foundryup` tool. The people at paradigm are really going in-character – the logo of Foundry is a smith and it comes with two tools called `forge` and `cast`. 


![Foundry Logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xd9pb4qvle0l0u119vnb.jpg)

These tools are used for “forging” the contract (development, testing and deploying) and “casting” the contract (interacting with the deployed contract). I wonder how long until the terms forging and casting smart contracts catch up with everyone (personally, I think it would be funny if 2 years down the line some releases a video or article where they say “forging a smart contract in hardhat” XD).

Also, there is this really awesome [Foundry book](https://book.getfoundry.sh) you might want to check out if you want to learn more. Apparently that trend is catching up and new languages, tools and frameworks publishing their docs as “books” with – [Rust Book](https://doc.rust-lang.org/book/), [Anchor Book](https://book.anchor-lang.com), [Nutanix Bible](https://www.nutanixbible.com) and so on already being available to learners.

## Housekeeping
In this section we will initiate a new Foundry project and fetch and install our dependencies. Since we won’t be discussing the contract `OurNFTContract.sol` in this article, this section just sets up the housekeeping for our mini-project in Foundry.

Let’s start with running `forge init` first. This is kind of like `npx hardhat init` and sets up the files required. Given that Foundry has this vibrant and active community, this project structure might change a bit as time passes. However, here are the basic rules:
1.	You write the contract you want to develop/deploy in the `src` directory (browse to project root folder).
2.	You write your tests inside the `test` directory. The tests are written inside a file in that directory. The file naming convention is **`<YOUR CONTRACT NAME>.t.sol`** where the **t** in-between denotes test for the respective contract in the src folder.
3.	The `.gitmodules` file like the `node_modules` file. The thing about Foundry is that it fetches all the modules you might want to use directly from GitHub. So, this file maintains those links.
4.	The `foundry.toml` file is the `package.jso`n counterpart and we define things like re-mappings (we will come to those) here.


We will place `OurNFTContract.sol` inside the `src` folder and our Mock VRF contract inside a directory called `mocks` inside the `src` folder itself. You won’t have the `mocks` folder when you initialize a new project. So, make it yourself. You can compile the contract by running forge build. Try running it now and you might face an error as shown below.


![Running forge build inside your Foundry Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jnysvwiau0s30cnwskfo.png)

Now if you have been following my articles, you should know by now that `OurNFTContract.sol` has two dependencies – Openzeppelin and Chainlink. It imports contracts from both projects. Normally, running a `npm install` in Hardhat or Truffle might have sufficed. But this is Foundry and it brings in its nuances.

To install the respective modules, we will need to first fetch the Github repo names to [Openzepplin](https://github.com/OpenZeppelin/openzeppelin-contracts) and [Chainlink](https://github.com/smartcontractkit/chainlink) (click on these hyperlinks). We will enter this when installing. To add it into this Foundry Project, run:
1.	`forge install Openzeppelin/openzeppelin-contracts`
2.	`forge install smartcontractkit/chainlink`

If it says that there are changes in the Project that needs to be commited, just run the above again by adding a `–no-commit` like the images shown below.


![No Commit Foundry Installation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u42iktmwnvlnvmghcsot.png)


![No Commit Foundry Installation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7klcy370ijlp8z4kl30z.png)

This will install Openzeppelin and Chainlink respectively. You might think that this should be enough. Run forge compile to check it. You will see it still errors out. You may go through the stack trace of the error before reading along.

Basically, what happens is that when the above two commands are run, forge fetches those repos, adds them to the `lib` directory. It then creates what is called a remapping. Whenever your import these contracts, it will map those import paths to the modules installed in the folders inside `lib`. Running `forge remappings` will show you those assumed re-mappings.

![forge Remappings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8412kw49yhp2ercyi4x5.png)

However, the there is a difference between node-based imports and imports in Foundry. Node-based imports start with an **_“@”_**. Foundry ones do not by default. So, there are two options in-front of you now. You could either change those node-based imports in `OurNFTContract.sol` from something like these:


![Node Based imports](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzcm9vhor04uzor4ltiu.png)

to the ones shown below where the “@” are removed as shown in the image below. In fact, if you run forge re-mappings you would notice that forge assumes these re-mappings when you install a module.


![Foundry Default Mapping Style](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbwfz5n5syw96i5odich.png)

Now let’s say you do not want to do the above. You could go with the second option. Open the `foundry.toml` file in your project root and add a re-mappings key along with the remapping you want as shown below:

![Adding Remappings in foundry.toml](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3rezr03msdq8fz938r6.png)

Now, running `forge build` should work. It should compile all the contracts and give a similar output to the one shown in the picture below:


![Running forge build to compile the contract](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zxp2uj6cvq9im9bjecon.png)

This concludes our housekeeping. Now that the contracts compile, we know that the re-mappings work. We can now move onto the bit that makes Foundry so interesting – writing tests. There are 2 USPs here:
1.	You can write tests in pure Solidity. So, bye bye ChaiJS.
2.	Foundry comes with a built-in Fuzzer. 

Fuzzing is basically when you supply all kinds of values to your contract to check which one breaks it. With Hardhat or Truffle you do not get the feature. You need to separately use Echidna to fuzz test your contracts. And while the [starter kit from Chainlink](https://github.com/smartcontractkit/hardhat-starter-kit) does make it easy by bringing the scripts under one `package.json`, you need Docker to run it. Fuzzing with Foundry is as simple as passing in an argument to the test functions and Foundry handles the rest by passing a range of values when running those functions.

## Contract Testing
As you may know by now, the general convention of creating a test file is by naming the file `<CONTRACT TO BE TESTED>.t.sol` which in our case turns out to be `OurNFTContract.t.sol` (placed inside the `test` directory).

The tests will be written in this folder and the contract name follows the convention `<CONTRACT TO BE TESTED>Test`. It will inherit from either `Test` or `DSTest` as shown below. `Test.sol` is to be imported from `forge-std` while `DSTest` from `ds-test`. You don’t need to use both since `Test.sol` already imports `DSTest.sol` and the `Test` Contract inherits from the same. The `Test` contract additionally contains what are known as **“Cheatcodes”** in Foundry. These are use to alter the EVM State. This is made possible as `Test` inherits from `Script` abstract contract that instantiates the `**vm**` object. The VM object helps us do things that can be done in hardhat through `ethers.provider.send(“evm_increaseTime”, <Time Period>)` and so on. The code below shows the initial portion of the Testing Contract in our case.

```javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

// import "ds-test/test.sol";
import "forge-std/Test.sol";
import "../src/OurNFTContract.sol";
import "../src/mocks/VRFCoordinatorV2Mock.sol";

contract OurNFTContractTest is Test {
    OurNFTContract nftContract;
    VRFCoordinatorV2Mock vrfContract;

    event SubscriptionCreated(uint64 indexed subId, address owner);
    event SubscriptionFunded(
        uint64 indexed subId,
        uint256 oldBalance,
        uint256 newBalance
    );
    event ConsumerAdded(uint64 indexed subId, address consumer);
    event RandomWordsRequested(
        bytes32 indexed keyHash,
        uint256 requestId,
        uint256 preSeed,
        uint64 indexed subId,
        uint16 minimumRequestConfirmations,
        uint32 callbackGasLimit,
        uint32 numWords,
        address indexed sender
    );
    event RandomWordsFulfilled(
        uint256 indexed requestId,
        uint256 outputSeed,
        uint96 payment,
        bool success
    );

    event ReceivedRandomness(uint256 reqId, uint256 n1, uint256 n2);
    event RequestedRandomness(uint256 reqId, address invoker, string name);

    function setUp() public {
        vrfContract = new VRFCoordinatorV2Mock(0, 0);
        vrfContract.createSubscription();
        nftContract = new OurNFTContract(
            1,
            address(vrfContract),
            bytes32(
                0x4b09e658ed251bcafeebbc69400383d49f344ace09b9576fe248bb02c003fe9f
            )
        );
    }

/*
* Our Tests
*/

}
```

The Testing contract contains a lot of events as shown above. This is because there is no `.to.emit` clause from ChaiJS that can be used here. There is a specific syntax for catching and checking events which we will come to later in this article that requires such explicit event declarations.

**_Notice how in the above code there is no constructor._** That’s because a Testing Contract needs to contain a `setUp()` function which does that job essentially. It’s like the `beforeEach` Hook of Chai. Foundry runs the `setUp()` function for every test function written in the Testing contract. Thus, every test function gets a clean slate to execute. This is a boon and a bane as we will come to see.

In our case, the `setUp()` function consist of deploying the Mock VRF contract, creating a subscription in it and then initializing the our NFT Contract with the Subscription ID, Mock VRF address and the Keyhash. The `testSubscriptionCreate()` function below shows how a subscription creation emits an Event that can then be caught for testing purposes.

```javascript
    function testSubscriptionCreate() public {
        vm.expectEmit(true, true, false, false);
        emit SubscriptionCreated(2, address(this));
        vrfContract.createSubscription();

        (uint96 balance, uint64 reqCount, address owner, ) = vrfContract
            .getSubscription(2);

        // emit log_uint(balance);
        // emit log_uint(reqCount);
        // emit log_address(owner);

        assertEq(balance, 0);
        assertEq(reqCount, 0);
        assertEq(owner, address(this));
    }
```

You may know by now that events can have at most 3 indexed parameters. The rest is the data in the event. The `vm.expectEmit()` in the code above has been set to `(true, true, false, false)`. The first 3 args passed to `vm.expectEmit()` can be thought of positional args which enable the check for the 3 indexes in the events. This tells that Foundry needs to check that the 3 indexes should match between the event that we emit in the next line and the event that is emitted in the line after that on Mock VRF contract function invocation. The fourth/last arg to `vm.expectEmit()` tell it if it needs to also check the data in the events as well (non-indexed parameters).

In our case, `SubscriptionCreated` event emitted from the Mock VRF (when a subscription is created) has 2 parameters both of which are indexed. So we set the first 2 args to `vm.expectEmit()` to `true`. There is no 3rd parameter in the event. In fact, there is not data parameter either if you think about it. So, we set them both to `false`.

In the line following `vm.expectEmit()`, we emit the event from the `TestContract`. This tells Foundry the event topic it needs to watch out for along with the parameters. After that we invoke `vrfContract.createSubscription()` which creates a sub and emits the event.

We then check if the subscription was really created by invoking the `getSubscription()` function and asserting that the values match out expectations. The 3 commented out events in-between which start with `**log_**` act as a console.log (Javascript) and can be viewed when the verbosity is set on `forge test`.

In the code below, we try to successfully fund the subscription. Note that there is no subscription with Id 2 by the time this function is executed. This is because the state from the previous function execution is wiped clean. We still have a subscription with ID 1 since that is created in the `setUp()` function that runs before every test function.

```javascript
    function testSubscriptionFund() public {
        vm.expectEmit(true, true, true, false);
        emit SubscriptionFunded(1, 0, 2 ether);
        vrfContract.fundSubscription(1, 2 ether);
    }
```

Notice, how we again try an event-based testing in the code above. We could also for the alternative where after executing `vrfContract.fundSubsciption()` we execute `vrfContract.getSubscription()` passing in the ID 1. We could then assert that the balance was increased as we expected. This presents a nice choice and you can go for both. Here, it’s a personal choice to check using the `SubscriptionFunded` event emitted when `vrfContract.fundSubscription()` is executed.

After this, we check the consumer add procedure also in an event-based manner. Keep in mind that by the time this test is executed, the subscription with ID 1 has again reverted back to the 0-balance state from the previous state where we funded it with 2 ether-precision of fake LINK tokens.

```javascript
    function testAddConsumer() public {
        vm.expectEmit(true, false, false, true);
        emit ConsumerAdded(1, address(nftContract));
        vrfContract.addConsumer(1, address(nftContract));
    }
```

The `vrfContract.fundSubscription()` and `vrfContract.addConsumer()` will be executed in every test here on out so I wanted to elaborate on them right here before we get into the crux. As with the article testing the Chainlink VRF functionality, we will not check for everything but just that the communication between our NFT contract and the Mock VRF contract is functional.

## NFT Contract sends request successfully
The first phase in the cycle is when our NFT contract requests a random number. This is done when the `safeMint()` function is invoked. I won’t explain the `safeMint()` function. For that I will refer you to my [Chainlink VRF series](https://dev.to/abhikbanerjee99/series/18388) once again.

```javascript
    function testRequestIsSent() public {
        vrfContract.fundSubscription(1, 2 ether);
        vrfContract.addConsumer(1, address(nftContract));

        // vm.prank(address(1));
        vm.expectEmit(false, false, false, true);
        emit RequestedRandomness(1, address(this), "Halley");
        nftContract.safeMint("Halley");
    }
```

Bottom line, the `safeMint()` function emits a `RequestedRandomness` function which we try to detect in the test above. Note how the EVM is told to ignore all the index parameters and just check the data parameters in the event 1:1 to the event we emit from this function. This is because I didn’t put any indexed parameters in the event when I was developing the NFT contract (What? Were you expecting a long-winded discussion on best practices and stuff? Sorry to disappoint XD).

There is also a commented out `vm.prank()` which we will come to in the later sections in this article.

If the test detects the event mentioned above, we can be sure that the NFT contract has indeed requested for Random Numbers from the VRF Oracle. 

## Mock VRF Coordinator Receives request successfully
Next, we check if the Mock VRF Oracle (`vrfContract`) receives the requests from our NFT contract. The code section below should appear identical to the one above albeit one change. The event is different. The `RandomWordsRequested` event is emitted from the Mock VRF when it receives a request for Random Numbers.

```javascript
    function testRequestIsReceived() public {
        vrfContract.fundSubscription(1, 2 ether);
        vrfContract.addConsumer(1, address(nftContract));

        // vm.prank(address(1));
        vm.expectEmit(true, true, true, false);
        emit RandomWordsRequested(
            bytes32(
                0x4b09e658ed251bcafeebbc69400383d49f344ace09b9576fe248bb02c003fe9f
            ),
            1,
            100,
            1,
            0,
            0,
            0,
            address(nftContract)
        );
        nftContract.safeMint("Halley");
    }

```

Note how in the code section above, we tell the EVM to test if the event from the Mock VRF contract matches the event we emit in this test with respect to all the indexed parameters but it can ignore other event parameters. This is done by `vm.expectEmit(true, true, true, false)`. The `RandomWordsRequested` event has key hash, subscription Id and the address of the contract which sends the random number request as indexed parameters (check in the code section above where we defined all the events). These are duly matched.

There is a difference of how the events are detected. If you were using ChaiJs, you would have to pass in the contract name followed by the event name to the `.to.emit` clause. Well…

![We don't do that here](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rw4kq815n5klz59xv2mv.png)

## Mock VRF Coordinator Sends back response successfully
Now that our NFT Contract has successfully sent the request for Random Numbers to Mock VRF and the Mock VRF has received the same, we need to check if the Mock VRF processes that request and sends back the response successfully to the NFT contract. The test given below does that.

```javascript
    function testRequestIsProcessed() public {
        vrfContract.fundSubscription(1, 2 ether);
        vrfContract.addConsumer(1, address(nftContract));

        // IMPORTANT: Test Will fail with "ERC721: transfer to non ERC721Receiver implementer" otherwise
        vm.prank(address(1));
        nftContract.safeMint("Halley");

        vm.expectEmit(true, false, false, true);
        emit RandomWordsFulfilled(1, 1, 0, true);
        vrfContract.fulfillRandomWords(1, address(nftContract));
    }
```

Among the things to note here is how we can finally uncommented the `vm.prank(address(1))` line. This indicates that the next contract/function call will happen from the address passed to `vm.prank()` function. The need for this is explored in my very first article – [NFT Marketplace Code Along](https://dev.to/abhikbanerjee99/building-your-nft-marketplace-from-scratch-code-along-42ah). Basically, we will need to implement the IERC721Receiver interface or inherit from [ERC721Holder](https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721Holder). 

You could try to run the tests but you would encounter an error similar to the one shown below.

![ERC721Holder Absence Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7f0wa5eggp3ab1rgpoh.png)

Every function call to contracts inside the Testing contracts takes place from the address of the Testing Contract. When we invoke `nftContract.safeMint()`, the NFT contract receives a mint request from this testing contract address. The thing is safe minting or transferring to contract addresses is not allowed unless it implements the aforementioned interface.

So we use `vm.prank()` here to spoof the `safeMint()` call such that it does not come from contract address. This is equivalent to using `.connect()` on a deployed address in Hardhat while testing to invoke a contract function call from a different address.

Now the question arises why didn’t we need it in the earlier two tests? That’s because this kind of a check is put inside the `_mint()` function of `ERC721.sol` which we inherit. We do not invoke that from `safeMint()` but from `fulfillRandomWords()` function of our NFT contract.

The execution of the `fulfillRandomWords()` from the Mock VRF contract results in the emission of the `RandomWordsFulfilled` event which we check in the test above.

## NFT Contract receives the response successfully
Lastly, the NFT contract needs to receive the random numbers from the Mock VRF. In _**our**_ NFT contract, on execution of the `fulfillRandomWords()` function, we emit the `ReceivedRandomness` event. _You may know by now that the Mock VRF is responsible for the execution of fulfillRandomWords() in NFT contract._ 

```javascript
    function testResponseIsReceived() public {
        vrfContract.fundSubscription(1, 2 ether);
        vrfContract.addConsumer(1, address(nftContract));

        // IMPORTANT: Test Will fail with "ERC721: transfer to non ERC721Receiver implementer" otherwise
        vm.prank(address(1));
        nftContract.safeMint("Halley");

        vm.expectEmit(false, false, false, true);
        emit ReceivedRandomness(
            1,
            uint256(keccak256(abi.encode(1, 0))),
            uint256(keccak256(abi.encode(1, 1)))
        );
        vrfContract.fulfillRandomWords(1, address(nftContract));

        (address currentOwner, , string memory name, , , ) = nftContract
            .getCharacter(0);
        assertEq(currentOwner, address(1));
        assertEq(name, "Halley");
    }
```

So, as shown above, if we detect the emission of `ReceivedRandomness` event on execution of `fulfillRandomWords()` in the Mock VRF contract then we can be sure that the NFT contract has received the random words. That is precisely what we check. Furthermore, if we invoke `getCharacter()` of the NFT contract by passing in the Token ID, then we can be doubly sure that the process runs full circle. That is the extra mile we go in the above test.

A simple question may be **_“How do we know the exact Random numbers received?”_**. When querying and actual Chainlink VRF Oracle, we won’t know it. But in the Mock VRF contract, to make things easy, the process is deterministic. It is done by encoding the request ID and the index of the number in the request. The encoded value’s Keccak hash is obtained and then typecasted to `uint256`. Kind of like the picture below. Since we know the request ID and this logic in the Mock VRF, we can predict those numbers.

This concludes the initial goal of replicating those ChaiJS tests from Hardhat in Foundry.

## Extra: Mocking VRF with Foundry’s Fuzzer
For everything we did in this article, we have not tested out one of the crucial offerings of Foundry – its fuzzing ability. There’s no extra fuss for fuzzing your contract. All you need to do is write a test that accepts a parameter. Foundry will take care of the rest. It will pass in values to check if any of those break your contract functionality.

> Where might we be able to use that now?

Turns out we can emulate the Mock VRF to fuzz test our NFT contract. The code below demonstrates how. At this point you need to be aware of the fact that the Mock VRF contract calls the `rawFulfillRandomWords()` function on the contract requesting randomness. This function is inherited from `VRFConsumerBaseV2` (all contracts using Chainlink’s VRF must inherit from this contract). Only the VRF Coordinator is allowed to call the `rawFulfillRandomWords()` function.

```javascript
    function testFuzzOnNftContract(uint n1, uint n2) public {
        // vm.assume(n1 > 0 && n2 > 0);

        vrfContract.fundSubscription(1, 2 ether);
        vrfContract.addConsumer(1, address(nftContract));

        // IMPORTANT: Test Will fail with "ERC721: transfer to non ERC721Receiver implementer" otherwise
        vm.prank(address(1));
        nftContract.safeMint("Halley");

        uint[] memory randomNumbers = new uint256[](2);
        randomNumbers[0] = n1;
        randomNumbers[1] = n2;

        vm.prank(address(vrfContract));
        nftContract.rawFulfillRandomWords(1, randomNumbers);

        (address currentOwner, , string memory name, , , ) = nftContract
            .getCharacter(0);
        assertEq(currentOwner, address(1));
        assertEq(name, "Halley");
    }

```

Remember how `vm.prank()` helps us spoof the `msg.sender` address for the next contract call? We use that in the above test. We trick our NFT contract into thinking that the Mock VRF contract is indeed the one calling the `rawFulfillRandomWords()` inside our NFT contract. Through this, we pass in the fuzz values to the function in an `uint256` array (just like VRF Oracle would send it).

If you do not, the below error would be thrown:

![Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58e4umr17ah06ofx5knu.png)

If you use the `-vvvvv` flag with `forge test` you will get to see the stack trace of the error which will pin out the following:

![Verbose Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phwest5c3o4fkrhginds.png)

You could also use `vm.assume()` to skip for certain fuzz values. It is kind of like a require statement but at the EVM level. If the condition inside `vm.assume()` evaluates to false, the fuzz values passes to the testing function in the current iteration are skipped. 

Lastly, we check if the character was indeed minted as we wanted by calling on the `getCharacter()` method in our NFT Contract.
You could actually go a step further and replace the Mock VRF Contract completely by using `vm.mockCalls()`. This would be stepping into the Black-box kind of test setup. In my opinion it would defeat the purpose here because we won’t be fully using Chainlink’s VRF process even though we have access to it via the Mock VRF Contract.

Running `forge test` now would run the whole batch of tests we went through in this article. Rest assured, it should show the below output:

![Running `forge test`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4aro4p1lf44dv4mlz40.png)

You can see the gas consumed by different tests shown beside each test. That's another advantage of using Foundry. No need to install a plugin manually like Hardhat.

## Contract Deployment
We are nearing the end. Lastly, we need to deploy the contract now that it has been tested. You can use **forge** for this purpose. In the code repo for this blog, I have provided the shell script which sets a few environment variables needed by Foundry for contract deployment and verification.

Given the length of this article, we will deploy and verify in one go. Run the `env.sh` script in the code repo after putting in your respective values. The following command deploys the contract to the network of your choice (obtained from the environment variables). 

> `forge create --rpc-url $ETH_RPC_URL --constructor-args 0 0 --private-key $PRIVATE_KEY OurNFTContract --verify`

In our case, that is Polygon’s Mumbai Testnet. The `–verify` flag at the end of the command tells forge to verify the contract as well. This is a departure from the two-step process we generally see with Hardhat. Of course, you can verify separately as well.


![Smart contract deployment with Foundry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcp8sanjwh98q54d6dqx.png)
As you can see above, our contract gets deployed and verified.

## That’s it for this one, folks!
I believe in being classy. I am not crass enough to put forward comparison like "Who is the best footballer - Messi or Ronaldo?" (of course its Iker Casillas, period). Even though I have given a comparison between Foundry and Hardhat, I would not say outright that one is better than the other. They both have their advantages. Having used Hardhat for so long, I would say it has grown on me. But Foundry too has its appeal. I will leave it up to you as to which one to use.

This concludes our journey at this time. We started from initializing a Foundry project all the way to deploying it after testing. If you were able to follow it in one go, kudos to you. The code for this blog has been open-sourced at [this](https://github.com/abhik-99/Foundry-Blog) repo.

If you have any feedback or would like to have a discussion regarding this article, feel free to leave them below. If you have any topic recommendations for me, I can be contacted via my email and social handles. Until the next article, keep building awesome stuff on Web3 and WAGMI!