Using Foundry Start to End with Phron

Introduction

Foundry has become an increasingly popular development environment for smart contracts because it requires only one language: Solidity. Phron offers introductory documentation on using Foundry with Phron networks, which is recommended to read to get an introduction to using Foundry. In this tutorial, we will dip our toes deeper into the library to get a more cohesive look at properly developing, testing, and deploying with Foundry.

In this demonstration, we will deploy two smart contracts. One is a token, and the other will depend on that token. We will also write unit tests to ensure the contracts work as expected. To deploy them, we will write a script that Foundry will use to determine the deployment logic. Finally, we will verify the smart contracts on Phron's blockchain explorer.

Checking Prerequisites

To get started, you will need the following:

  • Have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet

  • To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers

  • Have a Phron API Key

Create a Foundry Project

The first step to start a Foundry project is, of course, to create it. If you have Foundry installed, you can run:

forge init foundry && cd foundry

This will have the forge utility initialize a new folder named foundry with a Foundry project initialized within it. The script, src, and test folders may have files in them already. Be sure to delete them, because we will be writing our own soon.

From here, there are a few things to do first before writing any code. First, we want to add a dependency to OpenZeppelin's smart contracts, because they include helpful contracts to use when writing token smart contracts. To do so, add them using their GitHub repository name:

forge install OpenZeppelin/openzeppelin-contracts

This will add the OpenZeppelin git submodule to your lib folder. To be sure that this dependency is mapped, you can override the mappings in a special file, remappings.txt:

Every line in this file is one of the dependencies that can be referenced in the project's smart contracts. Dependencies can be edited and renamed so that it's easier to reference different folders and files when working on smart contracts. It should look similar to this with OpenZeppelin installed properly:

Finally, let's open up the foundry.toml file. In preparation for Etherscan verification and deployment, add this to the file:

The first addition is a specification of the solc_version, underneath profile.default. The rpc_endpoints tag allows you to define which RPC endpoints to use when deploying to a named network, in this case, Phron. The etherscan tag allows you to add Etherscan API keys for smart contract verification, which we will review later.

Add Smart Contracts

Smart contracts in Foundry destined for deployment by default belong in the src folder. In this tutorial, we'll write two smart contracts. Starting with the token:

Open the file and add the following to it:

As you can see, the OpenZeppelin ERC20 smart contract is imported by the mapping defined in remappings.txt.

The second smart contract, which we'll name Container.sol, will depend on this token contract. It is a simple contract that holds the ERC-20 token we'll deploy. You can create the file by executing:

Open the file and add the following to it:

The Container smart contract can have its status updated based on how many tokens it holds and what its initial capacity value was set to. If the number of tokens it holds is above its capacity, its status can be updated to Overflowing. If it holds tokens equal to capacity, its status can be updated to Full. Otherwise, the contract will start and stay in the Unsatisfied state.

Container requires a MyToken smart contract instance to function, so when we deploy it, we will need logic to ensure that it is deployed with a MyToken smart contract.

Write Tests

Before we deploy anything to a TestNet or MainNet, however, it's good to test your smart contracts. There are many types of tests:

  • Unit tests — allow you to test specific parts of a smart contract's functionality. When writing your own smart contracts, it can be a good idea to break functionality into different sections so that it is easier to unit test

  • Fuzz tests — allow you to test a smart contract with a wide variety of inputs to check for edge cases

  • Integration tests — allow you to test a smart contract when it works in conjunction with other smart contracts, so that you know it works as expected in a deployed environment

    • Forking tests - integration tests that allows you to make a fork (a carbon copy of a network), so that you can simulate a series of transactions on a preexisting network

Unit Tests in Foundry

To get started with writing tests for this tutorial, make a new file in the test folder:

By convention, all of your tests should end with .t.sol and start with the name of the smart contract that it is testing. In practice, the test can be stored anywhere and is considered a test if it has a function that starts with the word "test".

Let's start by writing a test for the token smart contract. Open up MyToken.t.sol and add:

Let's break down what's happening here. The first line is typical for a Solidity file: setting the Solidity version. The next two lines are imports. forge-std/Test.sol is the standard library that Forge (and thus Foundry) includes to help with testing. This includes the Test smart contract, certain assertions, and forge cheatcodes.

If you take a look at the MyTokenTest smart contract, you'll see two functions. The first is setUp, which is run before each test. So in this test contract, a new instance of MyToken is deployed every time a test function is run. You know if a function is a test function if it starts with the word "test", so the second function, testConstructorMint is a test function.

Great! Let's write some more tests, but for Container.

And add the following:

This test smart contract has two tests, so when running the tests, there will be two deployments of both MyToken and Container, for four smart contracts. You can run the following command to see the result of the test:

When testing, you should see the following output:

Test Harnesses in Foundry

Sometimes you'll want to unit test an internal function in a smart contract. To do so, you'll have to write a test harness smart contract, which inherits from the smart contract and exposes the internal function as a public one.

For example, in Container, there is an internal function named _isOverflowing, which checks to see if the smart contract has more tokens than its capacity. To test this, add the following test harness smart contract to the Container.t.sol file:

Now, inside of the ContainerTest smart contract, you can add a new test that tests the previously unreachable _isOverflowing contract:

Now, when you run the test with forge test, you should see that testIsOverflowingFalse passes!

Fuzzing Tests in Foundry

When you write a unit test, you can only use so many inputs to test. You can test edge cases, a few select values, and perhaps one or two random ones. But when working with inputs, there are nearly an infinite amount of different ones to test! How can you be sure that they work for every value? Wouldn't you feel safer if you could test 10000 different inputs instead of less than 10?

One of the best ways that developers can test many inputs is through fuzzing, or fuzz tests. Foundry automatically fuzz tests when an input in a test function is included. To illustrate this, add the following test to the MyTokenTest contract in MyToken.t.sol.

This test includes uint256 amountToMint as input, which tells Foundry to fuzz with uint256 inputs! By default, Foundry will input 256 different inputs, but this can be configured with the FOUNDRY_FUZZ_RUNS environment variable.

Additionally, the first line in the function uses vm.assume to only use inputs that are less than or equal to one ether since the mint function reverts if someone tries to mint more than one ether at a time. This cheatcode helps you direct the fuzzing into the right range.

Let's look at another fuzzing test to put in the MyTokenTest contract, but this time where we expect to fail:

In Foundry, when you want to test for a failure, instead of just starting your test function with the world "test", you start it with "testFail". In this test, we assume that the amountToMint is above one ether, which should fail!

Now run the tests:

You should see something similar to the following in the console:

Forking Tests in Foundry

In Foundry, you can locally fork a network so that you can test out how the contracts would work in an environment with already deployed smart contracts. For example, if someone deployed smart contract A on Phron that required a token smart contract, you could fork the Phron network and deploy your own token to test how smart contract A would react to it.

Note

Phron's custom precompile smart contracts currently do not work in Foundry forks because precompiles are Substrate-based whereas typical smart contracts are completely based on the EVM. Learn more about forking on Phron and the differences between Phron and Ethereum.

In this tutorial, you will test how your Container smart contract interacts with an already deployed MyToken contract on Phron

Let's add a new test function to the ContainerTest smart contract in Container.t.sol called testAlternateTokenOnPhronFork:

The first step (and thus first line) in this function is to have the test function fork a network with vm.createFork. Recall that vm is a cheat code provided by the Forge standard library. All that's necessary to create a fork is an RPC URL, or an alias for an RPC URL that's stored in the foundry.toml file. In this case, we added an RPC URL for "phron" in the setup step, so in the test function we will just pass the word "phronbase". This cheat code function returns an ID for the fork created, which is stored in an uint256 and is necessary for activating the fork.

On the second line, after the fork has been created, the environment will select and use the fork in the test environment with vm.selectFork. The third line just demonstrates that the current fork, retrieved with vm.activeFork, is the same as the Phron fork.

The fourth line of code retrieves an already deployed instance of MyToken, which is what's so useful about forking: you can use contracts that are already deployed.

The rest of the code tests capacity like you would expect a local test to. If you run the tests (with the -vvvv tag for extra logging), you'll see that it passes:

That's it for testing! You can find the complete Container.t.sol and MyToken.t.sol files below:

Container.t.sol
MyToken.t.sol

Deploy in Foundry with Solidity Scripts

Not only are tests in Foundry written in Solidity, the scripts are too! Like other developer environments, scripts can be written to help interact with deployed smart contracts or can help along a complex deployment process that would be difficult to do manually. Even though scripts are written in Solidity, they are never deployed to a chain. Instead, much of the logic is actually run off-chain, so don't worry about any additional gas costs for using Foundry instead of a JavaScript environment like Hardhat.

Deploy on Phron

In this tutorial, we will use Foundry's scripts to deploy the MyToken and Container smart contracts. To create the deployment scripts, create a new file in the script folder:

By convention, scripts should end with s.sol and have a name similar to the script they relate to. In this case, we are deploying the Container smart contract, so we have named the script Container.s.sol, though it's not the end of the world if you use a more suitable or descriptive name.

In this script, add:

Let's break this script down. The first line is standard: declaring the solidity version. The imports include the two smart contracts you previously added, which will be deployed. This includes additional functionality to use in a script, including the Script contract.

Now let's look at the logic in the contract. There is a single function, run, which is where the script logic is hosted. In this run function, the vm object is used often. This is where all of the Forge cheatcodes are stored, which determines the state of the virtual machine that the solidity is run in.

In the first line within the run function, vm.envUint is used to get a private key from the system's environment variables (we will do this soon). In the second line, vm.startBroadcast starts a broadcast, which indicates that the following logic should take place on-chain. So when the MyToken and the Container contracts are instantiated with the new keyword, they are instantiated on-chain. The final line, vm.stopBroadcast ends the broadcast.

Before we run this script, let's set up some of our environment variables. Create a new .env file:

And within this file, add the following:

Note

Foundry provides additional options for handling your private key. It is up to you to decide whether or not you would rather use it in the console, have it stored in your device's environment, using a hardware wallet, or using a keystore.

To add these environment variables, run the following command:

Now your script and project should be ready for deployment! Use the following command to do so:

This command runs the ContainerDeployScript contract as a script. The --broadcast option tells Forge to allow broadcasting of transactions, the --verify option tells Forge to verify to Phronscan when deploying, -vvvv makes the command output verbose, and --rpc-url phronbase sets the network to what phronbase was set to in foundry.toml. The --legacy flag instructs Foundry to bypass EIP-1559. While all Phron networks support EIP-1559, Foundry will refuse to submit the transaction to phronbase and revert to a local simulation if you omit the --legacy flag.

You should see something like this as output:

You should be able to see that your contracts were deployed, and are verified on Phronscan! For example, this is where my Container.sol contract was deployed.

The entire deployment script is available below:

Container.s.sol

Deploy on Phron MainNet

Let's say that you're comfortable with your smart contracts and you want to deploy on the Phron MainNet! The process isn't too different from what was just done, you just have to change the command's rpc-url from phronbase to phron, since you've already added Phron MainNet's information in the foundry.toml file:

It's important to note that there are additional, albeit more complex, ways of handling private keys in Foundry. Some of these methods can be considered safer than storing a production private key in environment variables.

That's it! You've gone from nothing to a fully tested, deployed, and verified Foundry project. You can now adapt this so that you can use Foundry in your own projects!

This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.

Last updated