Compiling and testing Vyper contract using Foundry
Tags: vyper, foundry, smartcontract, ethereum
Published: 2022-08-22 14:08:00

In this tutorial, we will use [vyper](https://vyper.readthedocs.io/en/stable/) to write our smart contract and [Foundry](https://book.getfoundry.sh/) as our Ethereum Development Environment. As we'll be using Foundry, we'll be writing our tests in Solidity.

### What is Vyper?
Vyper is a contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine. The aim of vyper is to make smart contract maximally human-readable. Any person with low prior experience with vyper or any particular language should understand vyper.
Vyper follows python3 syntax. Thus, it should be noted that vyper syntax is a valid python3 syntax, however not all python3 syntax is valid in vyper.

### Why Foundry?
Hardhat is definitely a nice Ethereum Development Environment but [Foundry](https://book.getfoundry.sh/) has some more cool benefits to offer.
- Unlike Hardhat, [Foundry](https://book.getfoundry.sh/) allows us to write tests as well as scripts in Solidity
- Foundry comes with the Forge Standard library, which allows using [`Dappsys Test`](https://book.getfoundry.sh/reference/ds-test) to assert values.
- We can simply log values using `emit_log(string)`, `emit_int(int_value)`, `emit_uint(uint_value)`, `emit_address(address_value)`, etc. to log values of different data types
- To get more detailed information on the tests, we can simply use the `-v` flag after the test command. More the number of `v` will give us more detailed info. Example: `forge test -vvv`. We can add up to five `v` flags.
- We can mock a user (or we can say an account) using [`.prank`](https://book.getfoundry.sh/cheatcodes/prank.html) and [`.startPrank`](https://book.getfoundry.sh/cheatcodes/start-prank.html).
- We can set nounce, block number, and timestamp for an account.
- Foundry gives us the detailed gas report for our tests by running the below command.
```shell
forge test --gas-report
```
- Foundry provides us **Fuzzing** that allows us to define parameters for our test function and it will populate those parameters values on runtime

### Getting Started
Create a new project using Foundry by running the following command.

> Note: You can install and get vyper and foundry running locally by following [vyper](https://vyper.readthedocs.io/en/stable/installing-vyper.html) and [foundry](https://book.getfoundry.sh/getting-started/installation.html) docs.

> You can refer to [this](https://github.com/Nazeeh21/vyper-foundry) repo to follow along.

```sh
forge init vyper_foundry
cd vyper_foundry
```
> Note: If you want to know and understand more about foundry and its structure, please refer to [this](https://nazeeh.hashnode.dev/writing-and-deploying-smart-contracts-using-foundry-thirdweb-deploy) article.

We have foundry up and running, let's write smart contract using Vyper. Navigate to the `src` folder in your root directory and rename `Contract.sol` to `CountContract.vy` as we will be writing our Contract in vyper. Paste the below Contract into that file.

```py
count: public(uint256)

@external
def __init__(_count: uint256):
    self.count = 1

@external
def setCount(_count: uint256):
    self.count = _count

@external
def increment():
    self.count += 1
  
@external
def decrement():
    self.count -= 1

@external
def getCount() -> uint256: 
    return self.count
```

Let me give a quick overview of our Contract. We have defined a `count` variable that keeps track of our count. We have defined functions that lets us `increment`, `decrement`, `set` and `get` the value of our `count` variable. The `__init__` function we have defined will run as a constructor for our contract, where we are setting the value for our `count` variable by taking it as an argument. Vyper follows Python3 syntax thus it's really easy to write and understand the smart contract.

### Compiling vyper contracts
As we've written our smart contract, let's compile our smart contract. We can compile our smart contract written in vyper by running the following command in our terminal.

```shell
vyper src/CountContract.vy
```
You should see the following output in your terminal. This is the bytecode of our Contract.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yg1qy2z4afwt7i1ga0pj.png)  

We are able to compile our Contract, but how we'll be able to compile it using foundry because foundry doesn't have a default script for compiling vyper contracts? For that, we'll be using [**ffi**](https://book.getfoundry.sh/cheatcodes/ffi) from foundry. **ffi** allows us to call an arbitrary command in foundry if enabled. Thus, using this feature we'll run `vyper src/CountContract.vy` command.  So let's write a deployer contract that allows us to deploy our contract. Navigate to the root dir of our project and create a new folder named `utils` and under that create `vyperDeployer.sol` file. Paste the following code from below into that file.

{% gist https://gist.github.com/Nazeeh21/bf62b5ae4c6c7f5e4bab8103dc5182f6 file=vyperDeployer.sol %}

As you can see, on lines 21 and 50, we've defined the `deployContract` function which we'll use to deploy our vyper contract. The first function defined on line 21 will get executed when we don't need to pass arguments to the contract's constructor otherwise we'll use the second function defined on line 50 when we need to pass arguments to our contract's constructor. Both these functions will return an address at which our contract will get deployed.

We have our deployer contract ready. So let's write tests for our smart contract. But before writing our test, we need an interface to interact with our vyper contract. This will allow Foundry to interact with your Vyper contract, enabling the full testing capabilities that Foundry has to offer. For that, create an `interface` folder inside the `test` directory and inside `interface` create a file named `ICountContract.sol`. In this file, paste the code below.

```sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;

interface ICountContract {
    function setCount(uint256 _count) external;

    function increment() external;

    function decrement() external;

    function getCount() external returns (uint256);
}
```
We'll use this interface to interact with our `CountContract`. We've our interface ready, so let's write tests for it in the `Contract.t.sol` file inside the `test` directory. For that, paste the below code in that file.

```sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../utils/vyperDeployer.sol";
import "./interface/ICountContract.sol";

contract ContractTest is Test {
    VyperDeployer deployer = new VyperDeployer();
    ICountContract countContract;

    function setUp() public {
        countContract = ICountContract(deployer.deployContract('CountContract', abi.encode(1)));
    }

    function testIncrement() public {
        assertEq(countContract.getCount(), 1);
        countContract.increment();
        assertEq(countContract.getCount(), 2);
    }

    function testDecrement() public {
        assertEq(countContract.getCount(), 1);
        countContract.decrement();
        assertEq(countContract.getCount(), 0);
    }

    function testSetCount() public {
        assertEq(countContract.getCount(), 1);
        countContract.setCount(5);
        assertEq(countContract.getCount(), 5);
    }
}
```
As you can see, in our `setUp()` function, we are deploying a new instance of our `CountContract` and as a second argument to our `deployContract` method from our `deployer`, we are passing `abi.encode(1)`. This value will be passed as an argument to our constructor. Thus, the default value for our `count` variable in our contract will be 1 and we will be asserting values for our tests on the basis of this initial value.

Let's understand what is happening here and how our deployer works. We are creating a new instance of `VyperDepolyer` and we're calling the `deployer.deployContract(fileName)` method and we're passing the name of the contract we want to deploy. Our contract requires constructor arguments so we'll pass them in the abi encoded representation of the arguments which looks like this `deployer.deployContract('CountContract', abi.encode(1))`. The `deployContract` function compiles the Vyper contract and deploys the newly compiled bytecode, returning the address that the contract was deployed to. The deployed address is then used to initialize the ICountContract interface. Once the interface has been initialized, your Vyper contract can be used within Foundry like any other Solidity contract.

Now, we have everything ready required for our vyper contract. So, now let's run the tests and see how it goes. Run the following command in your terminal for that.

```shell
forge test
```
You should see an error like below in your terminal
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwmut3gbzqp6vcftg5jd.png)

The reason for this is that we have not enabled `ffi` in the foundry. To enable that we should run our test command with the `ffi` flag. So now run the following command.

```shell
forge test --ffi
```
Now, you should see all our tests passing in our terminal as shown below
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fznd8cr5ahkzz673ghro.png) 

### Deploying a vyper contract
Once you are done with writing and testing the vyper contract, you can deploy it to any network of your choice. 

The first option is to take the bytecode generated by the vyper compiler and manually deploy it through mist or geth.
```sh
vyper yourFileName.vy
# returns bytecode
```
Second option is to take the byte code and ABI and deploy it with your current browser on [myetherwallet’s](https://www.myetherwallet.com/) contract menu.
```sh
vyper -f abi yourFileName.vy
# returns ABI
```
This way you can write, test and deploy your vyper contract starting from scratch.

Congratulations! 🎉🎉🎉 You have just written a vyper contract and wrote tests and deploy scripts for it in foundry.

A huge shoutout to [0xKitsune](https://twitter.com/0xKitsune) for developing this setup and helping out with this article.