Turbo Charging Your Development Workflow
Contract Deployments Suck... But They Don't Have To!
In this article we explore the laborious task of contract deployment and how the team working on Ether.Fi use foundry's scripting tools, bash scripts and github automated workflows to make contract deployment a breeze.
The Old Way Of Things
Previously, when the team at Linum Labs deployed a suite of contracts, it would be an error prone and time consuming process. Developers would deploy their contracts, using the framework of their choice, and would then have to manually record the deployed contract addresses and communicate this to the front-end developers after the fact. ABI's could only be extracted once deployment was completed and then syncing the Subgraph can be done. This lead to inefficient communication between team members and repositories that were out of sync with each other.
A New Way Of Doing Things
Lets look at how the Ether.Fi team created an automatic workflow that dramatically reduces deployment time.
The Deploy Script
Foundry has some amazing tooling for deploying contracts and first among those is Foundry's scripting tools. We can now pull environment variables, initialize contracts, call any setter functions in our contract and verify our contracts on etherscan all from one easy to write script. Lets take a look at how this is achieved. (Scripts are written in Solidity, so no need for context switching!)
An example of our deploy script
First things first... Let's deploy our contracts.
We need to inherit the Script contract that comes out of the box when you install Foundry. This gives us access to the run() function which enables us to deploy and initialize our contracts.
Inside our run function, the first thing we need to do is retrieve the private key which will be used to deploy our contracts and will act as the owner() of the deployed contracts should it be needed. We use the vm. cheat code to access our .env file with vm.envUint("PRIVATE_KEY") where PRIVATE KEY is the variable in which the deployer's private key has been stored.
Then we broadcast our transaction to the blockchain with the vm.broadcast cheat code using the private key set in the previous line as an argument.
We can then deploy our contracts and assign them to a variable using the new keyword and passing in any necessary parameters. NewContract contract = new NewContract(parameter);
If needed, we can set any dependencies after all our contracts are deployed by calling the setter using the contract variable name with the below syntax.
Writing To A Release Folder
We can use Forge's vm.readFile and vm.writeFile to create a release logs folder to store all of our contract addresses as well as a release version number. This provides easy access to the latest deployed addresses for all developers to query as needed.
To be able to read and update a our version number, we create a function called stringToUint which casts our read version number from a string to an unsigned integer for us to interact with. We can then increment our version and write a new .release file with the latest version number appended and the latest deployed contract addresses stored inside. The release logs folder structure can be customized to be as granular as is needed.
Using Bash Script to extract ABI's and creating a makefile
NB: You will need the jq utility installed to be able to run this bash script.
Next, we create a new file named makefile in our project root. Inside it we can add all calls for jobs we would like to run.
For now, lets add some calls to deploy our contract, extract its ABI's and afterward write to a release file.
We can add the two commands together in our makefile to deploy our contracts with Foundry and extract their ABI's with a bash script in one call. The call could look something like this:
Let's break this call down...
Firstly, we give the call a name as our makefile can contain more than one operation. In the above, we name our call "deploy-goerli-early-reward-pool" as it describes what we are doing "deploy", which network we are deploying to "goerli" adn which contracts we are deploying. In this case our contract is the "early-reward-pool".
Then we make the call to Foundry's Forge module to run our deploy script:
We run this via our terminal with one simple command:
Congratulations! You've deployed your contracts to a testnet and extracted their ABI's for the front end to use.
As we've seen, we can add calls to our makefile in order to execute jobs automatically. Lets add all the jobs we'll require to deploy our contracts from end to end.
Creating A Github Workflow To Take Things To Another Level
Now we can create a github workflow by creating a new file with the .yml extension. In this file we add the calls from our makefile we would like to make and assign them to a github action.
For example, our .yaml file could look something like this:
Here we can see that we've set this to run whenever a workflow dispatches and there are a list of jobs that get run. So whenever the team deploys a version of the early adopter pool contract to Mainnet or a Testnet, this workflow runs and automatically compiles, tests the entire suite, generates ABIs for all contracts deploys the contract and makes a PR with updated contract addresses to the repo, ready to be merged. Whenever anyone needs to query the latest deployment, all they need to do is check the release folder for the latest deploy and interact with the given addresses.
The workflow can be run by any team member with access to the github actions of the contracts repository:
No more waiting and manually passing deployed addresses around... What used to take a few hours now takes a handful of minutes. Contract deployment supercharged!
Would you like to build your next web 3.0 project with us? Book a consultation here