Introduction
If you hail from the Web2 background, you’ll probably understand the relevance of security and code analysis in software development particularly where the code is meant to handle large volumes of data. Don’t get it twisted, Web3 is not an exception. Further diligence and extra care are required where Web3 comes to play. In 2022, report shows the industry lost around $2Billion to exploiters due to smart contract vulnerabilities and bugs. As Celo developers, it then becomes pertinent to exercise precautions and due diligence while building for the blockchain.
What the post is all about
As an expert in smart contract development, I prioritize security over other aspects which is why I have prepared this guide to awaken your consciousness for security while building on the Celo network. Itemized below is a list of tutorials I have written about using security tools. You can get started with any of them.
- Smart Contract Security on Celo with SmartCheck
- Smart Contract Security on Celo with Securify
- Smart contract security on Celo with Mythril
- Smart Contract Security on Celo with Manticore
- Analyzing smart contracts security on Celo using Slither
Prerequisites
This guide is well-suited for the intermediate-level developer hence the only condition for attempting it is to have experience working with Solidity programming language.
Requirements
Walking you through the steps required for setting up an environment to use sFuzz to detect vulnerabilities in your smart contracts, the following is needed prior to the next stage.
Note: This guide does not include testing smart contracts. It is assumed that you already know how to deploy contracts to the Celo networks, and can test them for correctness. Check Build feature-rich, persistent dapp on celo using wagmi to get started.
- A Linux-based OS. If you’re a window user, Please refer to setting up WSL.
sFuzz
sFuzz is one of the fuzzing tools for detecting smart contracts’ weaknesses or rather put “vulnerabilities”. Fuzzing also called fuzz testing is an automated method used in software testing that searches for weaknesses by injecting several unexpected, invalid, or malformed inputs into the system to test its security strengths.
Installation
Installation of sFuzz can be done in two ways but in this guide, we will only use the first method.
- Using image
- Installing from the source code
Preparing and setting up a project
Before installing sFuzz, let’s get our environment ready. We will use Ubuntu - a Linux-based operating system. If you’re on Windows, please follow the guide on the official page to install WSL so you can be able to run Linux OS on your machine. If you wonder which version of Ubuntu I used, Ubuntu v 20.04 was good for me.
Create a fresh project folder. I named mine smart-contract-security-on-celo-with-sfuzz
mkdir smart-contract-security-on-celo-with-sfuzz
Instaling sFuzz using image
To use this method, Docker
is required. If you don’t have docker installed, follow the steps below otherwise skip this part. The steps explain how to install and use Docker directly from the Ubuntu terminal without using the Docker desktop. Me, I prefer this method since the Docker desktop consumes a lot of my system resources.
Installing docker on Ubuntu
Docker is an application that eases the processes of building applications all done in containers. Containers are isolated environments for running applications for several reasons such as avoiding dependency clash etc. They’re synonymous with virtual machines, but containers are more resource-friendly, more portable, and are often more dependent on the host operating system.
To get docker running on Ubuntu, perform the following steps. Also, you might want to ensure the Ubuntu server is well set up by following the steps highlighted here
-
Update your packages. This is the first thing you’d want to do before performing any installation on Ubuntu
sudo apt update
-
Install prerequisite packages to allow
apt
use packages over HTTPS:sudo apt install apt-transport-https ca-certificates curl software-properties-common
-
We need the CGP key for official Docker repository
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg ```
-
The next step is to add the Docker repository we imported to apt sources
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
We’ve got a few additions, so let’s update our packages so they can be recognized
sudo apt update
-
Now we can register that we want to install Docker. Note we are getting it directly from the Docker repository. This is different from installing via Ubuntu
apt-cache policy docker-ce
-
You should see the registration success message in the terminal. The next step is to install Docker in Ubuntu. This command should start the Docker daemon, and enable it to start on boot.
sudo apt install docker-ce
-
We will confirm if Docker is running
sudo systemctl status docker
-
The output in the terminal should like this
docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2022-04-01 21:30:25 UTC; 22s ago TriggeredBy: ● docker.socket Docs: https://docs.docker.com Main PID: 7854 (dockerd) Tasks: 7 Memory: 38.3M CPU: 340ms CGroup: /system.slice/docker.service └─7854 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
-
Next is to add our username to the docker group so we can have access to run docker otherwise, we have to use
sudo
command each time we want to run docker commands.sudo usermod -aG docker <Add your username here>
Ex.
sudo usermod -aG docker bobelr
. At this point, you will need to log out of the Ubuntu server and log in again to effect the changes we made. -
Now type this command
su - <username previously created>
-
Lastly, to confirm if the username is added to the Docker group, run
group
Possible error
in the course of running the Docker command, you may encounter an error of this type
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Solution
To solve it, do the following:
sudo apt-get install cgroupfs-mount
sudo cgroupfs-mount
sudo service docker start
This will start the Docker daemon in the background. To confirm if Docker is running, and that we can access Docker to download images, run the following command.
docker run hello-world
Now that Docker is successfully installed, let’s go back to installing sFuzz using the Docker image.
-
Inside the project directory we created earlier, create a
contracts
folder where the Solidity files will be kept. So I havesmart-contract-security-on-celo-with-sfuzz/contracts/
mkdir contracts/
-
Create and add a contract file under the
contracts
folder. Name itBountyHunt.sol
-
Paste the sample code below
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; contract BountyHunt { mapping(address => uint) public bountyAmount; uint public totalBountyAmount; modifier preventTheft { _; if (address(this).balance < totalBountyAmount) revert(); } function grantBounty(address beneficiary, uint amount) public payable preventTheft { bountyAmount[beneficiary] += amount; totalBountyAmount += amount; } function claimBounty() public preventTheft { uint balance = bountyAmount[msg.sender]; (bool sent,) = msg.sender.call{value: balance}(""); if (sent) { totalBountyAmount -= balance; bountyAmount[msg.sender] = 0; } } function transferBounty(address to, uint value) public preventTheft { if (bountyAmount[msg.sender] >= value) { bountyAmount[to] += value; bountyAmount[msg.sender] -= value; } } }
What the code does
This is a sample BountyHunt contract for approving and claiming bounties. The contract was written with some bug intentions.
The contract holds two state variables declared as follows:
-
The public
bountyAmount
variable. A key-value pair mapping bounty hunters to their earnings.mapping(address => uint) public bountyAmount;
-
totalBountyAmount
variable tracks the total available bounty payouts at any point in time.uint public totalBountyAmount;
-
Modifier
preventTheft
is a check to ensure the total available bounty payable to hunters does not exceed the balance Celo balance of the contract.modifier preventTheft { _; if (address(this).balance < totalBountyAmount) revert(); }
-
The
grantBounty
function accepts abeneficiary
of an address type and anamount
as inputs. It awards theamount
value to the beneficiary account.function grantBounty(address beneficiary, uint amount) public payable preventTheft { bountyAmount[beneficiary] += amount; totalBountyAmount += amount; }
-
ClaimBounty()
accepts no argument, takes thepreventTheft
modifier and simply allows the caller to claim bounty reward.Tip: We didn’t implement any check-effect pattern here, hence pay attention to the risk.
function claimBounty() public preventTheft { uint balance = bountyAmount[msg.sender]; (bool sent,) = msg.sender.call{value: balance}(""); if (sent) { totalBountyAmount -= balance; bountyAmount[msg.sender] = 0; } }
-
transferBounty()
accepts an address and unsigned integer type allowing anyone to call it and transfer the bounty ofvalue
to any address of their choice.function transferBounty(address to, uint value) public preventTheft { if (bountyAmount[msg.sender] >= value) { bountyAmount[to] += value; bountyAmount[msg.sender] -= value; } }
Note: The above code is intended for this tutorial and is not meant for production.
-
Pull the docker image
docker pull sfuzz/ethfuzzer
-
The next step is to start the container. But we will need to map the path to the contracts folder to the container so that when docker is invoked, it already loaded all of the Solidity files in the contracts. We will perform all of these in one step.
docker run -it -v /replace-with-path-to-the-contract-folder/:/home/contracts/ sfuzz/ethfuzzer
Note: You have to use the absolute path to the file you want to map to the container. To get an absolute path to a file in Ubuntu, use either of the following methods. Either of the commands will print the path to the terminal
- Using the
find
command$ find $PWD -type f -name <fileName>
- The
realpath
commandrealpath <fileName>
- Using the
readlink
commandreadlink -f <fileName>
Example:
Editing the command docker run -it -v /path/to/contracts/folder/:/home/contracts/ sfuzz/ethfuzzer
in my terminal, I have the path replaced as:
docker run -it -v /home/bobelr/celosage/smart-contract-security-on-celo-with-sfuzz/:/home/contracts/ sfuzz/ethfuzzer
To test the code in the contract file residing in the folder we mapped to the container, we have to run:
cd /home/ && ./fuzzer -g -r 0 -d 120 && chmod +x fuzzMe && ./fuzzMe
The command switches to the root user and invokes sfuzz on the contracts. You should see the result shown in the terminal that looks like the one shown in the image below.
Output
For image src, click on the image
The image displays the overall progress of the process being run.
sFuzz will generate a set of output files under the contract folders where the result of the tests are kept. It gives you the full details and extra data of what’s happening to your contracts relative to the tasks it was set to perform.
sFuzz is able to detect the following set of vulnerabilities.
- Gasless send
- Integer overflow
- Integer underflow
- Exception disorder
- Reentrancy
- Timestamp dependency
- Blocknumber dependency
- Dangerous delegatecall
- Freezing ether
Conclusion
From the set of vulnerabilities itemized above, if your contract is prone to any of them, sFuzz alerts you, so you can make proper amendments before deploying to the live network. Moreso, be aware that using sFuzz does not guarantee that your dApp is 100% safe. There are a couple of known and undiscovered bugs. Simplicity reduces the risk of attack. Modular programming enforces code readability. Use the best practices and approaches to writing secure smart contracts.
Next step
As a developer targeting the Celo blockchain, start using security tools to scrutinize your contracts. If you have questions you wish to ask, please leave a comment or contact us on Discord. We’ll be happy to hear from you. To learn more about Celo and how to build your dream project, please refer to the Celo documentation and the Celo Developers resources.
About the Author
Isaac Jesse , aka Bobelr is a smart contract/Web3 developer. He has been in the field since 2018, worked as an ambassador with several projects like Algorand and so on as a content producer. He has also contributed to Web3 projects as a developer.