Introduction
Smart contracts are an integral part of decentralized applications where the logic resides. Solidity as a famous smart contract language provides us with constructs, global variables, semantics, and syntaxes for writing instructions for the EVM-like blockchains that are termed smart contracts. Even though the solidity documentation highlights some of the best practices to use while writing contracts that target layers of the EVM-compatible blockchain, developers are expected to research further to ensure the best is deployed.
Prerequisites
In this guide, you will learn how to leverage one of the smart contracts analysis and security tools called Remix static analyzer
to avoid cheap and common mistakes often made by smart contract developers. You must at least be an intermediate working with smart contracts using solidity. If you’re new in this field, I recommend starting from here and here.
I also assume you know how to deploy contracts to the Celo networks. If you’re new to it, please check the below tutorials.
-
How to Deploy your Smart Contract to Celo using Java and Web3js by @Phenzic
-
Deploying Smart Contracts on Celo Using Foundry - A Step-by-Step Guide by @bobelr
-
How to Deploy a Smart Contract to the Celo Testnet using Hardhat by @iamoracle
Requirements
For this tutorial, you do not need to install any library or CLI tool. We’ll be using an online Integrated Development Environment (IDE) known as Remix
Remix static analyzer
According to the Remix NPM publication, Remix analyzer is a tool used for performing static analysis on Solidity smart contracts to check security vulnerabilities and bad development practices. There are practices that are considered bad when writing smart contracts. They leave room for attackers to easily exploit and part away with the treasure.
The static analyzer tool works underneath Remix IDE “Solidity static analysis” plugin which is used to run an analysis for a compiled contract according to selected modules. To understand how the tool works, let’s examine what static analysis
is.
Static analysis
Static analysis is a process of debugging solidity code by examining it for vulnerabilities or bad practices without actually executing the code. The tool uses the Solidity Static Analysis
plugin under the hood to perform static analysis on smart contracts written in Solidity.
The tool will check for the following parameters in your contracts.
- Transaction origin: It checks whether the
tx.origin
variable is used. - Check-Effect interaction: It searches for a potential reentrancy risk.
- Inline assembly: Using inline assembly has fair advantages coupled with risks that caused it to be discouraged often.
- Block timestamp: Since miners can influence timestamps to a certain degree, dependency on it is highly discouraged.
- Low-level calls: Except you know what you’re doing or you’re an experienced developer, you should not be using low-level calls.
- Blockhash: This is a global variable in Solidity whose usage as well is discouraged because it can be manipulated by the miner.
- Self-destruct: It has been established that contracts using
self-destruct
can be broken hence you have to be extremely careful while using it.
Installation
To use the Remix Static Analyzer tool, you have to follow these simple steps.
- Visit Remix Online Interface
- Click on the
plug-in
icon on the left-hand pane as shown in the image above. Scroll till you find theStatic Analyzer
plugin and activate it.
- Confirm it was added.
Transaction origin & Low-level calls
The code below displays where tx.origin
was used and a low-level
call made. There is a potential vulnerability in the withdraw
function as the client could be deceived the owner only is authorized to withdraw from the contract. The attacker could send a call from another attacking contract acting on behalf of the client.
Secondly, we used a low-level interaction to forward the $Celo in the contract to the caller when they invoke the withdraw()
. As an advanced solidity developer, I need to guard against the recipient account calling back into the contract should it not belong to them. You also want to ensure the call was successful by checking the first return value is not false since low-level calls do not revert the whole transaction if not successful.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
contract TxOrigin {
uint public depositCounter;
address payable owner;
constructor(address payable client) {
require(client != address(0), "Zero address");
owner = client;
}
function deposit() public payable {
require(msg.value > 0, "Please send some value");
depositCounter ++;
}
function withdraw(address withdrawTo, uint amount) public {
require(tx.origin == owner, "Disguised");
(bool sent,) = withdrawTo.call{value: amount}("");
require(sent);
}
}
Report
Check-Effect interaction
A reentrancy attack is possible when we fail to balance the interactions between the changes we made and the storage. An example is displayed in the simple contract below.
- The
savings
records users’ saving balances and the time it was made using structured data type. - The issues arose in the
withdraw
function. We first check that the user has enough savings to withdraw, and forwarded the requested amount before we later update their balances. If the recipient is a malicious contract account designed to steal from theReentrancy
contract, they would have succeeded even before the main call terminates.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
contract Reentrancy {
struct Balances {
uint value;
uint depositTime;
}
mapping(address =>Balances) private savings;
constructor() { }
function deposit() public payable {
uint amount = msg.value;
require(amount > 0, "Please send some value");
savings[msg.sender] = Balances(amount, _now());
}
function _now() internal view returns(uint) {
return block.timestamp;
}
function withdraw(address withdrawTo, uint amount) public {
require(savings[msg.sender].value > amount, "Disguised");
require(_now() + 86400 > savings[msg.sender].depositTime, "Only after 24 hours");
(bool sent,) = withdrawTo.call{value: amount}("");
require(sent);
savings[msg.sender].value -= amount;
}
}
Report
Check the warnings on the left.
Inline assembly, Blockhash, and Selfdestruct
blockhash
is used to access the last 256 block hashes, that is the aggregate hash for the hashes in a block. Miners are able to influence Blockhash to an extent. A miner could cleverly compute the block hash by simply adding up the information in the currently mined block to influence the outcome of a specific transaction in the current block.
The selfdestruct
is able to block calling contracts unexpectedly. You need to exercise more care especially if this contract is meant to be used by other contracts such as library contracts. Be aware that the selfdestruct
of the callee contract can leave callers in an inoperable state.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
contract Example {
function sumAsm(uint[] memory data) public pure returns (uint sum) {
for (uint i = 0; i < data.length; ++i) {
assembly {
sum := add(sum, mload(add(add(data, 0x20), mul(i, 0x20))))
}
}
}
function getBlockHash() public view returns(bytes32 result) {
result = blockhash(block.number);
}
function closeContract(address closeTo) public {
selfdestruct(payable(closeTo));
}
}
Report
Conclusion
You would notice that without executing any of the contracts, the plug-in has already gone ahead to detect the unacceptable practices we had used in our contracts.
What next?
As a developer, building safe and secure dApps on the Celo blockchain is paramount. Start leveraging some of the sophisticated tools such as the Remix static analyzer to scrutinize your contracts code.
To learn how to deploy your dream project on the Celo blockchain, visit the Celo documentation
About the Author
Isaac Jesse, aka Bobelr is an advanced smart contract/Web3 developer. He has been in the field since 2018. He’s been an ambassador for projects such as Algorand, AtomicWallet, DxChain, etc. as a content creator and developer ambassador. He has also contributed to Web3 projects.