CI/CD for DeFi projects on Solana: an Intro Guide

Bouwe Ceunen
Credix

--

At Credix, we have built a decentralized credit marketplace on Solana. This came with some challenges, especially bringing some web2 habits and best practices to web3, including CICD. We are today happy to share these with the Solana community. We’ll go over the things we did to get this up and running on GitHub Actions. Happy to hear your thoughts in the comments!

TL;DR

With the help of GitHub Actions, we managed to build a CICD pipeline that validates our code using Clippy, runs multiple test scenarios thanks to our custom testing script, and deploys if needed.

Tooling

Let’s first go over the tooling we’ve used, we will need this to better understand some of the decisions we have made along the way.

GitHub has some very useful built-in tooling, such as GitHub Actions, it enables us to run workflows when we push a branch, make a PR, etc. Every workflow is defined in its own file in the.github/workflows directory. In our case it’s very straightforward, we have a linting step, a CI (Continuous Integration) step, and a CD (Continuous Deployment) step.

GitHub Actions Pipeline

A lot of DeFi projects use Anchor as an abstraction layer on top of vanilla Solana, it takes care of some of the cumbersome tasks of building on Solana. I will not go into too much detail but it’s important to know we’re building with Anchor.

This is also a very important step before we start with CI. Every project should have some basic linting in place, with Solana projects, it’s no different. We use Clippy which will do a lot of linting and optimizations for us on our Rust code.

Branching

A short summary of our branching structure to also better understand the CICD pipeline discussed later. With web2, the basic branches we’re mostly in the companies I worked for, development -> acceptance -> production. This of course changes with web3 and we now have the same amount of branches but with different intentions, localnet -> devnet -> mainnet. The localnet branch isn’t deployed anymore, devnet branch runs CI and deploys to Solana devnet and mainnet only runs CI.

CICD

Below is our basic pipeline that will run on GitHub Actions on every PR we make and on every push to some of our major branches. We define some env variables that specify the Solana, Anchor, and rust toolchain versions.

We use GitHub Cache to cache certain results such as Yarn, Cargo, Anchor, Solana, etc. This enables us to save some time on CICD runs.

Let’s go over the steps we see above. We have 3 major steps; validate, ci, cd.

  • validate

It’s very easy to run cargo clippy -- -D warnings, -- -D warnings will fail if it outputs any warnings. Clippy will also fix some automatically for you with the --fix argument. This step enables us to always push linted code because our pipeline fails if this is not the case, according to Clippy.

  • ci

It was also important to us that we could run different test case scenarios. With Anchor, it’s easy to run an integration test with the built-in solana-test-validator, by executing anchor test. This will automatically spin up a validator and run a specific test specified in the Anchor.toml. It was becoming difficult for us to run different scenarios because all tests were done on the same validator, so each test was building on top of the previous one which was not ideal. We needed a way to clear the test validator again to start a new testing scenario. To solve this issue, we created an Anchor.toml.template which is just a copy of the original one with one important change, namely the [scripts] tag.

[scripts]
test = "npx ts-mocha -p ./tests/tsconfig.json -t 1000000 {FILE}"

This will help us to change what testing file we would like to run with a clean validator. It’s now possible to create as many testing files as we want with each testing a different user flow in the tests directory. After the initialization is done, it will run the test.sh script that you can find below. We are planning to remove the template file because we feel that there’s a more clean solution, but this works for us for now.

#!/bin/bash# unit tests
cargo test
# integration tests
test_result=0
for i in tests/**/*.e2e.ts;
do
cat ./Anchor.toml.template > ./Anchor.toml
case "$(uname -s)" in
Darwin)
sed -i '' "s#{FILE}#$i#g" ./Anchor.toml
;;
Linux)
sed -i "s#{FILE}#$i#g" ./Anchor.toml
;;
*)
exit 1
;;
esac
anchor test
test_result=$((test_result | $?))
done
exit $test_result
  • cd

Once all components are installed fresh are downloaded from the cache, it will run a deploy.sh script, this script will do some basic Anchor-specific things to the Anchor.toml file, build the program and deploy using the Anchor CLI commands. With Anchor you will have to set the cluster, so we do this by using the provider.cluster CLI argument. We also run solana balance to see how much SOL we have left, this is important to be able to deploy on devnet for example.

#!/bin/bash

if [ -z "$1" ]
then
echo "Please provide correct environment"
exit 1
fi

# overwrite environment
solana balance
solana config set --url "$1"
anchor build --program-name credix -- --features "$1" --no-default-features
anchor deploy --program-name credix --provider.cluster "$1"

We also use Cargo Features now so we can define in our code what lines to include based on what environment we want to deploy with #[cfg(feature = “mainnet”)] for example. The code snippet below changes the management keys based on which feature is enabled. Management keys are privileged keys that can execute admin-only instructions.

#[cfg(feature = "localnet")]
pub const MANAGEMENT_KEYS: [&str; 2] = [
...
];
#[cfg(feature = "devnet")]
pub const MANAGEMENT_KEYS: [&str; 2] = [
...
];
#[cfg(feature = "mainnet")]
pub const MANAGEMENT_KEYS: [&str; 3] = [
...
];

Conclusion

To ensure a seamless testing and deployment cycle, GitHub Actions or another CICD platform is definitely recommended. To make sure all code is also clean code, it’s also recommended to include Clippy into your pipeline.

About Credix

CREDIX is a decentralized credit platform that gives borrowers in emerging countries access to previously untapped capital. Our blockchain protocol provides credit lines to high-quality borrowers with an attractive yield for investors globally.

Be sure to pay us a visit at credix.finance and to follow us on twitter to stay updated.

--

--