In this tutorial, we will use vyper to write our smart contract and Foundry 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 has some more cool benefits to offer.
- Unlike Hardhat, Foundry allows us to write tests as well as scripts in Solidity
- Foundry comes with the Forge Standard library, which allows using
Dappsys 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 ofv
will give us more detailed info. Example:forge test -vvv
. We can add up to fivev
flags. - We can mock a user (or we can say an account) using
.prank
and.startPrank
. - 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 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.
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.
vyper src/CountContract.vy
You should see the following output in your terminal. This is the bytecode of our Contract.
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 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.
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.
// 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.
// 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.
forge test
You should see an error like below in your terminal
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.
forge test --ffi
Now, you should see all our tests passing in our terminal as shown below
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.
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 contract menu.
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 for developing this setup and helping out with this article.
Latest comments (0)